Merge remote-tracking branch 'refs/remotes/origin/master'
authorRobert Lipe <robertlipe@gpsbabel.org>
Tue, 15 Sep 2015 03:04:53 +0000 (22:04 -0500)
committerRobert Lipe <robertlipe@gpsbabel.org>
Tue, 15 Sep 2015 03:04:53 +0000 (22:04 -0500)
1  2 
garmin_gpi.cc
nmea.cc

diff --cc garmin_gpi.cc
index 0000000000000000000000000000000000000000,17e18f79954dcd595777c2db43c24c659ec9a52d..cae6f1d42ef8d295d6013db559fcf1338a80635d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1616 +1,1629 @@@
 -  int res;
+ /*
+     Support for Garmin Points of Interest (.gpi files)
+     Copyright (C) 2007 Olaf Klein, o.b.klein@gpsbabel.org
+     Copyright (C) 2007-2012 Robert Lipe, robertlipe+source@gpsbabel.org
+     This program is free software; you can redistribute it and/or modify
+     it under the terms of the GNU General Public License as published by
+     the Free Software Foundation; either version 2 of the License, or
+     (at your option) any later version.
+     This program is distributed in the hope that it will be useful,
+     but WITHOUT ANY WARRANTY; without even the implied warranty of
+     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+     GNU General Public License for more details.
+     You should have received a copy of the GNU General Public License
+     along with this program; if not, write to the Free Software
+     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+  */
+ /*
+       History:
+       * 2007/05/18: initial release (only a reader)
+       * 2007/05/20: added writer code with embedded bitmap
+       * 2007/05/22: add support for multiple bounding boxes
+                     (useful / required!) for large waypoints lists
+       * 2007/05/23: add optional user bitmap
+       * 2007/06/02: new method to compute center (mean) of bounds
+                     avoid endless loop in group splitting
+       * 2007/07/10: put address fields (i.e. city) into GMSD
+       * 2007/07/12: add write support for new address fields
+       * 2007/10/20: add option unique
+       * 2007/12/02: support speed and proximity distance (+ alerts)
+       * 2008/01/14: fix structure error after adding speed/proximity
+       * 2008/03/22: add options "speed" and "proximity" (default values) and "sleep"
+       ToDo:
+       * Display mode ("Symbol & Name") ??? not in gpi ???
+       * support category from GMSD "Garmin Special Data"
+ */
+ #include "defs.h"
+ #include "cet_util.h"
+ #include "jeeps/gpsmath.h"
+ #include "garmin_fs.h"
+ #include "garmin_gpi.h"
+ #include <stdlib.h>
+ #include <QtCore/QTextCodec>
+ #define MYNAME "garmin_gpi"
+ #define GPI_DBG 1
+ #undef GPI_DBG
+ #define DEFAULT_ICON  "Waypoint"
+ #define WAYPOINTS_PER_BLOCK   128
+ /* flags used in the gpi address mask */
+ #define GPI_ADDR_CITY         1
+ #define GPI_ADDR_COUNTRY      2
+ #define GPI_ADDR_STATE                4
+ #define GPI_ADDR_POSTAL_CODE  8
+ #define GPI_ADDR_ADDR         16
+ static char* opt_cat, *opt_pos, *opt_notes, *opt_hide_bitmap, *opt_descr, *opt_bitmap;
+ static char* opt_unique, *opt_alerts, *opt_units, *opt_speed, *opt_proximity, *opt_sleep;
+ static char* opt_writecodec;
+ static double defspeed, defproximity;
+ static int alerts;
+ static arglist_t garmin_gpi_args[] = {
+   {
+     "alerts", &opt_alerts, "Enable alerts on speed or proximity distance",
+     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {
+     "bitmap", &opt_bitmap, "Use specified bitmap on output",
+     NULL, ARGTYPE_FILE, ARG_NOMINMAX
+   },
+   {
+     "category", &opt_cat, "Default category on output",
+     "My points", ARGTYPE_STRING, ARG_NOMINMAX
+   },
+   {
+     "hide", &opt_hide_bitmap, "Don't show gpi bitmap on device",
+     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {
+     "descr", &opt_descr, "Write description to address field",
+     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {
+     "notes", &opt_notes, "Write notes to address field",
+     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {
+     "position", &opt_pos, "Write position to address field",
+     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {
+     "proximity", &opt_proximity, "Default proximity",
+     NULL, ARGTYPE_STRING, ARG_NOMINMAX
+   },
+   {
+     "sleep", &opt_sleep, "After output job done sleep n second(s)",
+     NULL, ARGTYPE_INT, "1", NULL
+   },
+   {
+     "speed", &opt_speed, "Default speed",
+     NULL, ARGTYPE_STRING, ARG_NOMINMAX
+   },
+   {
+     "unique", &opt_unique, "Create unique waypoint names (default = yes)",
+     "Y", ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {
+     "units", &opt_units, "Units used for names with @speed ('s'tatute or 'm'etric)",
+     "m", ARGTYPE_STRING, ARG_NOMINMAX
+   },
+   {
+     "writecodec", &opt_writecodec, "codec to use for writing strings",
+     "windows-1252", ARGTYPE_STRING, ARG_NOMINMAX
+   },
+   ARG_TERMINATOR
+ };
+ typedef struct {
+  public:
+   int D2;
+   char S3[9];         /* "GRMRECnn" */
+   time_t crdate;      /* creation date and time */
+   char POI[4];                /* "POI" */
+   char S8[3];
+   QString group;
+   QString category;
+ } reader_data_t;
+ typedef struct writer_data_s {
+   queue Q;
+   int ct;
+   int sz;
+   int alert;
+   bounds bds;
+   struct writer_data_s* top_left;
+   struct writer_data_s* top_right;
+   struct writer_data_s* buttom_left;
+   struct writer_data_s* buttom_right;
+ } writer_data_t;
+ typedef struct gpi_waypt_data_s {
+   int sz;
+   char* addr;
+   char* postal_code;
+ } gpi_waypt_data_t;
+ typedef struct {
+   int32_t size;
+   int16_t res1;
+   int16_t res2;
+   int32_t image_offset;
+   int32_t header_size;
+   int32_t width;
+   int32_t height;
+   int16_t planes;
+   int16_t bpp;
+   int32_t compression_type;
+   int32_t image_data_size;
+   int32_t resolution_h;
+   int32_t resolution_v;
+   int32_t used_colors;
+   int32_t important_colors;
+ } bmp_header_t;
+ typedef struct {
+   int16_t index;
+   int16_t height;
+   int16_t width;
+   int16_t line_sz;
+   int16_t bpp;
+   int16_t fixed_0;
+   int32_t image_size;
+   int32_t fixed_2c;
+   int32_t flag1;
+   int32_t tr_color;
+   int32_t flag2;
+   int32_t size_2c;
+ } gpi_bitmap_header_t;
+ typedef struct {
+   int sz;
+   int alerts;
+   short mask;
+   char addr_is_dynamic;
+   char* addr;
+   char* city;
+   char* country;
+   char* phone_nr;
+   char* postal_code;
+   char* state;
+ } gpi_waypt_t;
+ static gbfile* fin, *fout;
+ static int16_t codepage;      /* code-page, i.e. 1252 */
+ static reader_data_t* rdata;
+ static writer_data_t* wdata;
+ static short_handle short_h;
+ static char units;
+ static time_t gpi_timestamp = 0;
+ #ifdef GPI_DBG
+ # define PP warning("@%1$6x (%1$8d): ", gbftell(fin))
+ # define dbginfo warning
+ #else
+ # define PP
+ #endif
+ /*******************************************************************************
+ * %%%                             gpi reader                               %%% *
+ *******************************************************************************/
+ /* look for or initialize GMSD */
+ static garmin_fs_t*
+ gpi_gmsd_init(Waypoint* wpt)
+ {
+   garmin_fs_t* gmsd = GMSD_FIND(wpt);
+   if (wpt == NULL) {
+     fatal(MYNAME ": Error in file structure.\n");
+   }
+   if (gmsd == NULL) {
+     gmsd = garmin_fs_alloc(-1);
+     fs_chain_add(&wpt->fs, (format_specific_data*) gmsd);
+   }
+   return gmsd;
+ }
+ /* read a standard string with or without 'EN' (or whatever) header */
+ static char*
+ gpi_read_string_old(const char* field)
+ {
+   int l1;
+   char* res = NULL;
+   l1 = gbfgetint16(fin);
+   if (l1 > 0) {
+     short l2;
+     char first;
+     first = gbfgetc(fin);
+     if (first == 0) {
+       char en[2];
+       is_fatal((gbfgetc(fin) != 0),
+                MYNAME ": Error reading field '%s'!", field);
+       gbfread(en, 1, sizeof(en), fin);
+       l2 = gbfgetint16(fin);
+       is_fatal((l2 + 4 != l1),
+                MYNAME ": Error out of sync (wrong size %d/%d) on field '%s'!", l1, l2, field);
+       if ((en[0] < 'A') || (en[0] > 'Z') || (en[1] < 'A') || (en[1] > 'Z')) {
+         fatal(MYNAME ": Invalid country code!\n");
+       }
+       res = (char*) xmalloc(l2 + 1);
+       res[l2] = '\0';
+       PP;
+       if (l2 > 0) {
+         gbfread(res, 1, l2, fin);
+       }
+     } else {
+       res = (char*) xmalloc(l1 + 1);
+       *res = first;
+       *(res + l1) = '\0';
+       PP;
+       l1--;
+       if (l1 > 0) {
+         gbfread(res + 1, 1, l1, fin);
+       }
+     }
+   }
+ #ifdef GPI_DBG
+   dbginfo("%s: %s\n", field, (res == NULL) ? "<NULL>" : res);
+ #endif
+   return res;
+ }
+ static QString
+ gpi_read_string(const char* field)
+ {
+   char*s = gpi_read_string_old(field);
+   QString rv = STRTOUNICODE(s);
+   xfree(s);
+   return rv;
+ }
+ static void
+ read_header(void)
+ {
+   int len, i;
+ #ifdef GPI_DBG
+   struct tm tm;
+   char stime[32];
+ #endif
+   i = gbfgetint32(fin);
+   if (i != 0) {
+     i = gbfgetint32(fin);
+   }
+   rdata->D2 = gbfgetint32(fin);
+   gbfread(&rdata->S3, 1, sizeof(rdata->S3) - 1, fin); /* GRMRECnn */
+   if (strncmp(rdata->S3, "GRMREC", 6) != 0) {
+     fatal(MYNAME ": No GPI file!\n");
+   }
+   PP;
+   rdata->crdate = gbfgetint32(fin);
+ #ifdef GPI_DBG
+   tm = *localtime(&rdata->crdate);
+   tm.tm_year += 20;   /* !!! */
+   tm.tm_mday -= 1;    /* !!! */
+   strftime(stime, sizeof(stime), "%Y/%m/%d %H:%M:%S", &tm);
+   dbginfo("crdate = %lu (%s)\n", rdata->crdate, stime);
+ #endif
+   (void) gbfgetint16(fin);    /* 0 */
+   len = gbfgetint16(fin);
+   gbfseek(fin, len, SEEK_CUR);        /* "my.gpi" */
+   i =  gbfgetint32(fin);      /* 1 */
+   (void) gbfgetint32(fin);    /* 12 */
+   /* There are two dwords next.  On most typical files, they're
+   * "1" and "12".  On files from garminoneline.de/extras/poi, the
+   * next two words are "15" and "5" and there's 17 additional bytes
+   * that I can't identify.   So hardcode a seek here for now.
+   */
+   if (i == 15) {
+     gbfseek(fin, 17, SEEK_CUR);
+   }
+   gbfread(&rdata->POI, 1, sizeof(rdata->POI) - 1, fin);
+   if (strncmp(rdata->POI, "POI", 3) != 0) {
+     fatal(MYNAME ": Wrong or unsupported GPI file!\n");
+   }
+   for (i = 0; i < 3; i++) {
+     (void)gbfgetc(fin);
+   }
+   gbfread(&rdata->S8, 1, sizeof(rdata->S8) - 1, fin);
+   codepage = gbfgetint16(fin);
+   (void) gbfgetint16(fin);    /* typically 0, but  0x11 in
+                                       Garminonline.de files.  */
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("< leaving header\n");
+ #endif
+ }
+ /* gpi tag handler */
+ static int read_tag(const char* caller, const int tag, Waypoint* wpt);
+ /* read a single poi with all options */
+ static void
+ read_poi(const int sz, const int tag)
+ {
+   int pos, len;
+   Waypoint* wpt;
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("> reading poi (size %d)\n", sz);
+ #endif
+   PP;
+   len = 0;
+   if (tag == 0x80002) {
+     len = gbfgetint32(fin);   /* sub-header size */
+   }
+ #ifdef GPI_DBG
+   dbginfo("poi sublen = %1$d (0x%1$x)\n", len);
+ #endif
+   (void) len;
+   pos = gbftell(fin);
+   wpt = new Waypoint;
+   wpt->icon_descr = DEFAULT_ICON;
+   wpt->latitude = GPS_Math_Semi_To_Deg(gbfgetint32(fin));
+   wpt->longitude = GPS_Math_Semi_To_Deg(gbfgetint32(fin));
+   (void) gbfgetint16(fin);    /* ? always 1 ? */
+   (void) gbfgetc(fin);                /* seems to 1 when extra options present */
+   wpt->shortname = gpi_read_string("Shortname");
+   while (gbftell(fin) < (gbsize_t)(pos + sz - 4)) {
+     int tag = gbfgetint32(fin);
+     if (! read_tag("read_poi", tag, wpt)) {
+       break;
+     }
+   }
+   if (wpt->description.isEmpty() && !wpt->notes.isEmpty()) {
+     wpt->description = wpt->notes;
+   }
+   if (wpt->notes.isEmpty() && !wpt->description.isEmpty()) {
+     wpt->notes = wpt->description;
+   }
+   waypt_add(wpt);
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("< leaving poi\n");
+ #endif
+ }
+ /* read poi's following a group header */
+ static void
+ read_poi_list(const int sz)
+ {
+   int pos, i;
+   pos = gbftell(fin);
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("> reading poi list (-> %1$x / %1$d )\n", pos + sz);
+ #endif
+   PP;
+   i = gbfgetint32(fin);       /* mostly 23 (0x17) */
+ #ifdef GPI_DBG
+   dbginfo("list sublen = %1$d (0x%1$x)\n", i);
+ #else
+   (void) i;
+ #endif
+   (void) gbfgetint32(fin);    /* max-lat */
+   (void) gbfgetint32(fin);    /* max-lon */
+   (void) gbfgetint32(fin);    /* min-lat */
+   (void) gbfgetint32(fin);    /* min-lon */
+   (void) gbfgetc(fin);                /* three unknown bytes */
+   (void) gbfgetc(fin);                /* ? should be zero ? */
+   (void) gbfgetc(fin);
+   (void) gbfgetint32(fin);    /* ? const 0x1000100 ? */
+   while (gbftell(fin) < (gbsize_t)(pos + sz - 4)) {
+     int tag = gbfgetint32(fin);
+     if (! read_tag("read_poi_list", tag, NULL)) {
+       return;
+     }
+   }
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("< leaving poi list\n");
+ #endif
+ }
+ static void
+ read_poi_group(const int sz, const int tag)
+ {
+   int pos;
+   pos = gbftell(fin);
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("> reading poi group (-> %1$x / %1$d)\n", pos + sz);
+ #endif
+   if (tag == 0x80009) {
+     int subsz;
+     PP;
+     subsz = gbfgetint32(fin); /* ? offset to category data ? */
+ #ifdef GPI_DBG
+     dbginfo("group sublen = %d (-> %x / %d)\n", subsz, pos + subsz + 4, pos + subsz + 4);
+ #else
+     (void)subsz;
+ #endif
+   }
+   rdata->group = gpi_read_string("Group");
+   while (gbftell(fin) < (gbsize_t)(pos + sz)) {
+     int subtag = gbfgetint32(fin);
+     if (! read_tag("read_poi_group", subtag, NULL)) {
+       break;
+     }
+   }
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("< leaving poi group\n");
+ #endif
+ }
+ // TODO: 'tag' is probably not a 32 bit value.
+ // most likely it's a pair of 16's: the first pair is the tag number.
+ // if the second 16 is "eight", then it's an
+ // extended thingy and it has a 4-byte extended record length (total number
+ // of bytes for all record fields and all nested records, starting after the
+ // length field)
+ /* gpi tag handler */
+ static int
+ read_tag(const char* caller, const int tag, Waypoint* wpt)
+ {
+   int pos, sz, dist;
+   double speed;
+   short mask;
+   QString str;
+   char* cstr;
+   garmin_fs_t* gmsd;
+   sz = gbfgetint32(fin);
+   pos = gbftell(fin);
+ #ifdef GPI_DBG
+   PP;
+   dbginfo("%s: tag = 0x%x (size %d)\n", caller, tag, sz);
+ #endif
+   if ((tag >= 0x80000) && (tag <= 0x800ff)) {
+     sz += 4;
+   }
+   switch (tag) {
+   case 0x3:   /* size = 12 */
+   case 0x80003:       /* size = 12 */
+     dist = gbfgetint16(fin);          /* proximity distance in meters */
+     speed = (double)gbfgetint16(fin) / 100;   /* speed in meters per second */
+     if (dist > 0) {
+       WAYPT_SET(wpt, proximity, dist);
+     }
+     if (speed > 0) {
+       /* speed isn't part of a normal waypoint
+       WAYPT_SET(wpt, speed, speed);
+       */
+       if ((wpt->shortname.isEmpty()  || ((wpt->shortname).indexOf('@'))==-1)) {
+         if (units == 's') {
+           speed = MPS_TO_MPH(speed);
+         } else {
+           speed = MPS_TO_KPH(speed);
+         }
+         QString base = wpt->shortname.isEmpty() ? "WPT" : wpt->shortname;
+         wpt->shortname = base + QString("@%1").arg(speed,0,'f',0);
+       }
+     }
+     (void) gbfgetint32(fin);
+     (void) gbfgetint32(fin);
+     break;
+   case 0x4:   /* size = 2  ? */
+   case 0x6:   /* size = 2  ? */
+     break;
+   case 0x5:   /* group bitmap */
+     break;
+   case 0x7:
+     (void) gbfgetint16(fin);  /* category number */
+     rdata->category = gpi_read_string("Category");
+     break;
+   case 0xa:
+     wpt->description = gpi_read_string("Description");
+     break;
+   case 0xe:   /* ? notes or description / or both ? */
+     mask = gbfgetc(fin);
+     // Olaf's code called this a mask, but the bits below have nothing
+     // in common.  I'm wondering if that first byte is something else and
+     // a type e is always a note.
+     switch (mask) {
+     case 0x01:
+     case 0x05:
+     case 0x32:
+       str = gpi_read_string("Notes");
+     default:
+       break;
+     }
+     if (!wpt->description.isEmpty()) {
+       wpt->notes = str;
+     } else {
+       wpt->description = str;
+     }
+     break;
+   case 0x2:
+   case 0x80002:
+     read_poi(sz, tag);
+     break;
+   case 0x80008:
+     read_poi_list(sz);
+     break;
+   case 0x9:   /* ? older versions / no category data ? */
+   case 0x80009:       /* current POI loader */
+     read_poi_group(sz, tag);
+     break;
+   case 0x8000b:       /* address (street/city...) */
+     (void) gbfgetint32(fin);
+     // FALLTHROUGH
+   case 0xb:   /* as seen in German POI files. */
+     PP;
+     mask = gbfgetint16(fin); /* address fields mask */
+ #ifdef GPI_DBG
+     dbginfo("GPI Address field mask: %d (0x%02x)\n", mask, mask);
+ #endif
+     if ((mask & GPI_ADDR_CITY) && (cstr = gpi_read_string_old("City"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(city, cstr);
+     }
+     if ((mask & GPI_ADDR_COUNTRY) && (cstr = gpi_read_string_old("Country"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(country, cstr);
+     }
+     if ((mask & GPI_ADDR_STATE) && (cstr = gpi_read_string_old("State"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(state, cstr);
+     }
+     if ((mask & GPI_ADDR_POSTAL_CODE) && (cstr = gpi_read_string_old("Postal code"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(postal_code, cstr);
+     }
+     if ((mask & GPI_ADDR_ADDR) && (cstr = gpi_read_string_old("Street address"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(addr, cstr);
+     }
+     break;
+   case 0xc:
+     mask = gbfgetint16(fin);
+     if ((mask & 1) && (cstr = gpi_read_string_old("Phone"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(phone_nr, cstr);
+     }
+     if ((mask & 2) && (cstr = gpi_read_string_old("Phone2"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(phone_nr2, cstr);
+     }
+     if ((mask & 4) && (cstr = gpi_read_string_old("Fax"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(fax_nr, cstr);
+     }
+     if ((mask & 8) && (cstr = gpi_read_string_old("Email"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(email, cstr);
+     }
+     if ((mask & 0x10) && (str = gpi_read_string("Link"), !str.isEmpty())) {
+       waypt_add_url(wpt, str, str);
+     }
+     break;
+   case 0x8000c:       /* phone-number */
+     (void) gbfgetint32(fin);
+     PP;
+     mask = gbfgetint16(fin); /* phone fields mask */
+ #ifdef GPI_DBG
+     dbginfo("GPI Phone field mask: %d (0x%02x)\n", mask, mask);
+ #endif
+     if ((mask & 1) && (cstr = gpi_read_string_old("Phone"))) {
+       gmsd = gpi_gmsd_init(wpt);
+       GMSD_SET(phone_nr, cstr);
+     }
+     break;
+   case 0x80012:       /* ? sounds / images ? */
+     break;
+     /* Images? Seen in http://geepeeex.com/Stonepages.gpi */
+   case 0xd:
+     break;
+   case 0x11:
+   case 0x80007:
+     /* Looks like some kind of calendar information. */
+ #ifdef GPI_DBG
+   {
+     int x;
+     unsigned char* b = (unsigned char*) xmalloc(sz);
+     fprintf(stderr, "Tag: %x\n", tag);
+     gbfread(b, 1, sz, fin);
+     fprintf(stderr, "\n");
+     for (x = 0; x < sz; x++) {
+       fprintf(stderr, "%02x ", b[x]);
+     }
+     fprintf(stderr, "\n");
+     for (x = 0; x < sz; x++) {
+       fprintf(stderr, "%c", isalnum(b[x]) ? b[x] : '.');
+     }
+     fprintf(stderr, "\n");
+   }
+ #endif // GPI_DBG
+   break;
+   default:
+     warning(MYNAME ": Unknown tag (0x%x). Please report!\n", tag);
+     return 0;
+   }
+   gbfseek(fin, pos + sz, SEEK_SET);
+   return 1;
+ }
+ /*******************************************************************************
+ * %%%                             gpi writer                               %%% *
+ *******************************************************************************/
+ static void
+ write_string(const char* str, const char long_format)
+ {
+   int len;
+   len = strlen(str);
+   if (long_format) {
+     gbfputint32(len + 4, fout);
+     gbfwrite("EN", 1, 2, fout);
+   }
+   gbfputint16(len, fout);
+   gbfwrite(str, 1, len, fout);
+ }
+ static void
+ write_string(const QString& str, const char long_format)
+ {
+   write_string(STRFROMUNICODE(str), long_format);
+ }
+ static int
+ compare_wpt_cb(const queue* a, const queue* b)
+ {
+   const Waypoint* wa = (Waypoint*) a;
+   const Waypoint* wb = (Waypoint*) b;
+   return wa->shortname.compare(wb->shortname);
+ }
+ static char
+ compare_strings(const QString& s1, const QString& s2)
+ {
+   return s1.compare(s2);
+ }
+ static writer_data_t*
+ wdata_alloc()
+ {
+   writer_data_t* res;
+   res = (writer_data_t*) xcalloc(1, sizeof(*res));
+   QUEUE_INIT(&res->Q);
+   waypt_init_bounds(&res->bds);
+   return res;
+ }
+ static void
+ wdata_free(writer_data_t* data)
+ {
+   queue* elem, *tmp;
+   QUEUE_FOR_EACH(&data->Q, elem, tmp) {
+     Waypoint* wpt = (Waypoint*)elem;
+     if (wpt->extra_data) {
+       gpi_waypt_t* dt = (gpi_waypt_t*) wpt->extra_data;
+       if (dt->addr_is_dynamic) {
+         xfree(dt->addr);
+       }
+       xfree(dt);
+     }
+     delete wpt;
+   }
+   if (data->top_left) {
+     wdata_free(data->top_left);
+   }
+   if (data->top_right) {
+     wdata_free(data->top_right);
+   }
+   if (data->buttom_left) {
+     wdata_free(data->buttom_left);
+   }
+   if (data->buttom_right) {
+     wdata_free(data->buttom_right);
+   }
+   xfree(data);
+ }
+ static void
+ wdata_add_wpt(writer_data_t* data, Waypoint* wpt)
+ {
+   data->ct++;
+   ENQUEUE_TAIL(&data->Q, &wpt->Q);
+   waypt_add_to_bounds(&data->bds, wpt);
+ }
+ static void
+ wdata_check(writer_data_t* data)
+ {
+   queue* elem, *tmp;
+   double center_lat, center_lon;
+   if ((data->ct <= WAYPOINTS_PER_BLOCK) ||
+       /* avoid endless loop for points (more than WAYPOINTS_PER_BLOCK)
+          at same coordinates */
+       ((data->bds.min_lat >= data->bds.max_lat) && (data->bds.min_lon >= data->bds.max_lon))) {
+     if (data->ct > 1) {
+       sortqueue(&data->Q, compare_wpt_cb);
+     }
+     return;
+   }
+   /* compute the (mean) center of current bounds */
+   center_lat = center_lon = 0;
+   QUEUE_FOR_EACH(&data->Q, elem, tmp) {
+     Waypoint* wpt = (Waypoint*) elem;
+     center_lat += wpt->latitude;
+     center_lon += wpt->longitude;
+   }
+   center_lat /= data->ct;
+   center_lon /= data->ct;
+   QUEUE_FOR_EACH(&data->Q, elem, tmp) {
+     Waypoint* wpt = (Waypoint*) elem;
+     writer_data_t** ref;
+     if (wpt->latitude < center_lat) {
+       if (wpt->longitude < center_lon) {
+         ref = &data->buttom_left;
+       } else {
+         ref = &data->buttom_right;
+       }
+     } else {
+       if (wpt->longitude < center_lon) {
+         ref = &data->top_left;
+       } else {
+         ref = &data->top_right;
+       }
+     }
+     if (*ref == NULL) {
+       *ref = wdata_alloc();
+     }
+     data->ct--;
+     dequeue(&wpt->Q);
+     wdata_add_wpt(*ref, wpt);
+   }
+   if (data->top_left) {
+     wdata_check(data->top_left);
+   }
+   if (data->top_right) {
+     wdata_check(data->top_right);
+   }
+   if (data->buttom_left) {
+     wdata_check(data->buttom_left);
+   }
+   if (data->buttom_right) {
+     wdata_check(data->buttom_right);
+   }
+ }
+ static int
+ wdata_compute_size(writer_data_t* data)
+ {
+   queue* elem, *tmp;
++  int res = 0;
++
++  if (QUEUE_EMPTY(&data->Q))
++    goto skip_empty_block; /* do not issue an empty block */
+   res = 23;   /* bounds, ... of tag 0x80008 */
+   QUEUE_FOR_EACH(&data->Q, elem, tmp) {
+     Waypoint* wpt = (Waypoint*) elem;
+     gpi_waypt_t* dt;
+     garmin_fs_t* gmsd;
+     QString str;
+     res += 12;                /* tag/sz/sub-sz */
+     res += 19;                /* poi fixed size */
+     res += wpt->shortname.length();
+     if (! opt_hide_bitmap) {
+       res += 10;  /* tag(4) */
+     }
+     dt = (gpi_waypt_t*) xcalloc(1, sizeof(*dt));
+     wpt->extra_data = dt;
+     if (alerts) {
+ #if NEW_STRINGS
+ // examine closely.
+       const char* pos;
+       int pidx;
+       if ((pidx = wpt->shortname.indexOf('@')) != -1) {
+         pos = CSTR(wpt->shortname.mid(pidx));
+ #else
+       char* pos;
+       if ((pos = strchr(wpt->shortname, '@'))) {
+ #endif
+         double speed, scale;
+         if (units == 's') {
+           scale = MPH_TO_MPS(1);
+         } else {
+           scale = KPH_TO_MPS(1);
+         }
+         parse_speed(pos + 1, &speed, scale, MYNAME);
+         if (speed > 0) {
+           WAYPT_SET(wpt, speed, speed);
+         }
+ #if 0
+         if (pos > wpt->shortname) {
+           wpt->shortname[pos - wpt->shortname] = '\0';
+         }
+ #endif
+       } else if ((opt_speed) && (! WAYPT_HAS(wpt, speed))) {
+         WAYPT_SET(wpt, speed, defspeed);
+       }
+       if ((opt_proximity) && (! WAYPT_HAS(wpt, proximity))) {
+         WAYPT_SET(wpt, proximity, defproximity);
+       }
+       if ((WAYPT_HAS(wpt, speed) && (wpt->speed > 0)) ||
+           (WAYPT_HAS(wpt, proximity) && (wpt->proximity > 0))) {
+         data->alert = 1;
+         dt->alerts++;
+         res += 20;            /* tag(3) */
+       }
+     }
+     str = QString();
+     if (opt_descr) {
+       if (!wpt->description.isEmpty()) {
+         str = xstrdup(wpt->description);
+       }
+     } else if (opt_notes) {
+       if (!wpt->notes.isEmpty()) {
+         str = xstrdup(wpt->notes);
+       }
+     } else if (opt_pos) {
+       str = pretty_deg_format(wpt->latitude, wpt->longitude, 's', " ", 0);
+     }
+     if (!str.isEmpty()) {
+       dt->addr_is_dynamic = 1;
+       dt->addr = xstrdup(str);
+       dt->mask |= GPI_ADDR_ADDR;
+       dt->sz += (8 + strlen(dt->addr));
+     }
+     if ((gmsd = GMSD_FIND(wpt))) {
+       if ((dt->mask == 0) && ((dt->addr = GMSD_GET(addr, NULL)))) {
+         dt->mask |= GPI_ADDR_ADDR;
+         dt->sz += (8 + strlen(dt->addr));
+       }
+       if ((dt->city = GMSD_GET(city, NULL))) {
+         dt->mask |= GPI_ADDR_CITY;
+         dt->sz += (8 + strlen(dt->city));
+       }
+       if ((dt->country = GMSD_GET(country, NULL))) {
+         dt->mask |= GPI_ADDR_COUNTRY;
+         dt->sz += (8 + strlen(dt->country));
+       }
+       if ((dt->state = GMSD_GET(state, NULL))) {
+         dt->mask |= GPI_ADDR_STATE;
+         dt->sz += (8 + strlen(dt->state));
+       }
+       if ((dt->postal_code = GMSD_GET(postal_code, NULL))) {
+         dt->mask |= GPI_ADDR_POSTAL_CODE;
+         dt->sz += (2 + strlen(dt->postal_code));      /* short form */
+       }
+       if ((dt->phone_nr = GMSD_GET(phone_nr, NULL))) {
+         res += (12 + 4 +  strlen(dt->phone_nr));
+       }
+     }
+     if (dt->mask) {
+       dt->sz += 2;  /* + mask (two bytes) */
+     }
+     if (dt->sz) {
+       res += (dt->sz + 12);  /* + header size */
+     }
+     str = wpt->description;
+     if (str.isEmpty()) {
+       str = wpt->notes;
+     }
+ //            if (str && (strcmp(str, wpt->shortname) == 0)) str = NULL;
+     if (!str.isEmpty()) {
+       res += (12 + 4 + str.length());
+     }
+   }
++skip_empty_block:
++
+   if (data->top_left) {
+     res += wdata_compute_size(data->top_left);
+   }
+   if (data->top_right) {
+     res += wdata_compute_size(data->top_right);
+   }
+   if (data->buttom_left) {
+     res += wdata_compute_size(data->buttom_left);
+   }
+   if (data->buttom_right) {
+     res += wdata_compute_size(data->buttom_right);
+   }
+   data->sz = res;
++  if (QUEUE_EMPTY(&data->Q))
++    return res;
++
+   return res + 12;    /* + 12 = caller needs info about tag header size */
+ }
+ static void
+ wdata_write(const writer_data_t* data)
+ {
+   queue* elem, *tmp;
++  if (QUEUE_EMPTY(&data->Q))
++    goto skip_empty_block; /* do not issue an empty block */
++
+   gbfputint32(0x80008, fout);
+   gbfputint32(data->sz, fout);
+   gbfputint32(23, fout);      /* bounds + three bytes */
+   gbfputint32(GPS_Math_Deg_To_Semi(data->bds.max_lat), fout);
+   gbfputint32(GPS_Math_Deg_To_Semi(data->bds.max_lon), fout);
+   gbfputint32(GPS_Math_Deg_To_Semi(data->bds.min_lat), fout);
+   gbfputint32(GPS_Math_Deg_To_Semi(data->bds.min_lon), fout);
+   gbfputint32(0, fout);
+   gbfputint16(1, fout);
+   gbfputc(data->alert, fout);
+   QUEUE_FOR_EACH(&data->Q, elem, tmp) {
+     QString str;
+     int s0, s1;
+     Waypoint* wpt = (Waypoint*)elem;
+     gpi_waypt_t* dt = (gpi_waypt_t*) wpt->extra_data;
+     str = wpt->description;
+     if (str.isEmpty()) {
+       str = wpt->notes;
+     }
+     gbfputint32(0x80002, fout);
+     s0 = s1 = 19 + wpt->shortname.length();
+     if (! opt_hide_bitmap) {
+       s0 += 10;  /* tag(4) */
+     }
+     if (!str.isEmpty()) {
+       s0 += (12 + 4 + str.length());  /* descr */
+     }
+     if (dt->sz) {
+       s0 += (12 + dt->sz);  /* address part */
+     }
+     if (dt->phone_nr) {
+       s0 += (12 + 4 + strlen(dt->phone_nr));
+     }
+     if (dt->alerts) {
+       s0 += 20;  /* tag(3) */
+     }
+     gbfputint32(s0, fout);    /* size of following data (tag) */
+     gbfputint32(s1, fout);    /* basic size (without options) */
+     gbfputint32(GPS_Math_Deg_To_Semi(wpt->latitude), fout);
+     gbfputint32(GPS_Math_Deg_To_Semi(wpt->longitude), fout);
+     gbfputint16(1, fout);     /* ? always 1 ? */
+     gbfputc(alerts, fout);    /* seems to be 1 when extra options present */
+     write_string(wpt->shortname, 1);
+     if (dt->alerts) {
+       char flag = 0;
+       gbfputint32(3, fout);   /* tag(3) */
+       gbfputint32(12, fout);  /* always 12 */
+       if (WAYPT_HAS(wpt, proximity) && (wpt->proximity > 0)) {
+         gbfputint16((int) wpt->proximity, fout);
+         flag = 4;
+       } else {
+         gbfputint16(0, fout);
+       }
+       if (WAYPT_HAS(wpt, speed) && (wpt->speed > 0)) {
+         gbfputint16((int)(wpt->speed * 100), fout);
+         flag = 5;
+       } else {
+         gbfputint16(0, fout);
+       }
+       gbfputint32(0x100100, fout);    /* ??? */
+       gbfputc(1, fout);               /* ??? */
+       gbfputc(1, fout);               /* ??? */
+       gbfputc(flag, fout);
+       gbfputc(0x10, fout);            /* ??? */
+     }
+     if (! opt_hide_bitmap) {
+       gbfputint32(4, fout);   /* tag(4) */
+       gbfputint32(2, fout);   /* ? always 2 == version ??? */
+       gbfputint16(0, fout);
+     }
+     if (!str.isEmpty()) {
+       gbfputint32(0xa, fout);
+       gbfputint32(str.length() + 8, fout);    /* string + string header */
+       write_string(str, 1);
+     }
+     if (dt->sz) {                                     /* gpi address */
+       gbfputint32(0x8000b, fout);
+       gbfputint32(dt->sz, fout);
+       gbfputint32(0x2, fout);                 /* ? always 2 ? */
+       gbfputint16(dt->mask, fout);
+       if (dt->mask & GPI_ADDR_CITY) {
+         write_string(dt->city, 1);
+       }
+       if (dt->mask & GPI_ADDR_COUNTRY) {
+         write_string(dt->country, 1);
+       }
+       if (dt->mask & GPI_ADDR_STATE) {
+         write_string(dt->state, 1);
+       }
+       if (dt->mask & GPI_ADDR_POSTAL_CODE) {
+         write_string(dt->postal_code, 0);
+       }
+       if (dt->mask & GPI_ADDR_ADDR) {
+         write_string(dt->addr, 1);
+       }
+     }
+     if (dt->phone_nr) {
+       gbfputint32(0x8000c, fout);
+       gbfputint32(strlen(dt->phone_nr) + 2 + 2, fout);
+       gbfputint32(0x2, fout);                 /* ? always 2 ? */
+       gbfputint16(1, fout);                   /* mask */
+       write_string(dt->phone_nr, 0);
+     }
+   }
++skip_empty_block:
++
+   if (data->top_left) {
+     wdata_write(data->top_left);
+   }
+   if (data->top_right) {
+     wdata_write(data->top_right);
+   }
+   if (data->buttom_left) {
+     wdata_write(data->buttom_left);
+   }
+   if (data->buttom_right) {
+     wdata_write(data->buttom_right);
+   }
+ }
+ static void
+ write_category(const char* category, const unsigned char* image, const int image_sz)
+ {
+   int sz;
+   sz = wdata_compute_size(wdata);
+   sz += 8;    /* string header */
+   sz += strlen(opt_cat);
+   gbfputint32(0x80009, fout);
+   if ((! opt_hide_bitmap) && image_sz) {
+     gbfputint32(sz + image_sz + 8, fout);
+   } else {
+     gbfputint32(sz, fout);
+   }
+   gbfputint32(sz, fout);
+   write_string(opt_cat, 1);
+   wdata_write(wdata);
+   if ((! opt_hide_bitmap) && image_sz) {
+     gbfputint32(5, fout);
+     gbfputint32(image_sz, fout);
+     gbfwrite(image, 1, image_sz, fout);
+   }
+ }
+ static void
+ write_header(void)
+ {
+   time_t time = gpi_timestamp;
+   if (time != 0) {
+     struct tm tm;
+     tm = *gmtime(&time);
+     tm.tm_year -= 20;
+     time = mkgmtime(&tm);
+     time += SECONDS_PER_DAY;
+   }
+   gbfputint32(0, fout);
+   gbfputint32(0x16, fout);
+   gbfwrite("GRMREC00", 1, 8, fout);
+   gbfputint32(time, fout);
+   gbfputint16(0, fout);
+   gbfputint16(6, fout);
+   gbfwrite("my.gpi", 1, 6, fout);
+   gbfputint32(1, fout);
+   gbfputint32(0xc, fout);
+   gbfwrite("POI", 1, 3, fout);
+   gbfputc(0, fout);
+   gbfputc(0, fout);
+   gbfputc(0, fout);
+   gbfwrite("00", 1, 2, fout);
+   gbfputint16(codepage, fout);
+   gbfputint16(0, fout);
+ }
+ static void
+ enum_waypt_cb(const Waypoint* ref)
+ {
+   Waypoint* wpt;
+   queue* elem, *tmp;
+   QUEUE_FOR_EACH(&wdata->Q, elem, tmp) {
+     Waypoint* cmp = (Waypoint*) elem;
+     /* sort out nearly equal waypoints */
+     if ((compare_strings(cmp->shortname, ref->shortname) == 0) &&
+         (cmp->latitude == ref->latitude) &&
+         (cmp->longitude == ref->longitude) &&
+         (compare_strings(cmp->description, ref->description) == 0) &&
+         (compare_strings(cmp->notes, ref->notes) == 0)) {
+       return;
+     }
+   }
+   wpt = new Waypoint(*ref);
+   if (*opt_unique == '1') {
+     wpt->shortname = mkshort(short_h, wpt->shortname);
+   }
+   wdata_add_wpt(wdata, wpt);
+ }
+ static void
+ load_bitmap_from_file(const char* fname, unsigned char** data, int* data_sz)
+ {
+   gbfile* f;
+   int i, sz;
+   int dest_bpp;
+   int src_line_sz, dest_line_sz;
+   bmp_header_t src_h;
+   int* color_table = NULL;
+   gpi_bitmap_header_t* dest_h;
+   unsigned char* ptr;
+   f = gbfopen_le(fname, "rb", MYNAME);
+   is_fatal(gbfgetint16(f) != 0x4d42, MYNAME ": No BMP image.");
+   /* read a standard bmp file header */
+   src_h.size = gbfgetint32(f);
+   src_h.res1 = gbfgetint16(f);
+   src_h.res2 = gbfgetint16(f);
+   src_h.image_offset = gbfgetint32(f);
+   src_h.header_size = gbfgetint32(f);
+   src_h.width = gbfgetint32(f);
+   src_h.height = gbfgetint32(f);
+   src_h.planes = gbfgetint16(f);
+   src_h.bpp = gbfgetint16(f);
+   src_h.compression_type = gbfgetint32(f);
+   src_h.image_data_size = gbfgetint32(f);
+   src_h.resolution_h = gbfgetint32(f);
+   src_h.resolution_v = gbfgetint32(f);
+   src_h.used_colors = gbfgetint32(f);
+   src_h.important_colors = gbfgetint32(f);
+   /* Workaround for indexed BMP's with used_colors = 0 */
+   if ((src_h.bpp == 8) && (src_h.used_colors == 0)) {
+     src_h.used_colors = (src_h.image_offset - gbftell(f)) / 4;
+   }
+ #ifdef GPI_DBG
+   printf("data size:             0x%1$x (%1$d)\n", src_h.size);
+   printf("image data offset:     0x%1$x (%1$d)\n", src_h.image_offset);
+   printf("header size:           0x%1$x (%1$d)\n", src_h.header_size);
+   printf("image width:           0x%1$x (%1$d)\n", src_h.width);
+   printf("image height:          0x%1$x (%1$d)\n", src_h.height);
+   printf("number of planes:      0x%1$x (%1$d)\n", src_h.planes);
+   printf("bits per pixel:        0x%1$x (%1$d)\n", src_h.bpp);
+   printf("compression type:      0x%1$x (%1$d)\n", src_h.compression_type);
+   printf("image size:            0x%1$x (%1$d)\n", src_h.image_data_size);
+   printf("horizontal resolution: 0x%1$x (%1$d)\n", src_h.resolution_h);
+   printf("vertical resolution:   0x%1$x (%1$d)\n", src_h.resolution_v);
+   printf("number of colors:      0x%1$x (%1$d)\n", src_h.used_colors);
+   printf("important colors:      0x%1$x (%1$d)\n", src_h.important_colors);
+ #endif
+   /* sort out unsupported files */
+   if (!((src_h.width <= 24) && (src_h.height <= 24) &&
+         (src_h.width > 0) && (src_h.height > 0))) {
+     fatal(MYNAME ": Unsupported format (%dx%d)!\n", src_h.width, src_h.height);
+   }
+   if (!((src_h.bpp == 8) || (src_h.bpp == 24) || (src_h.bpp == 32))) {
+     fatal(MYNAME ": Unsupported color depth (%d)!\n", src_h.bpp);
+   }
+   if (!(src_h.compression_type == 0)) {
+     fatal(MYNAME ": Sorry, we don't support compressed bitmaps.\n");
+   }
+   if (src_h.used_colors > 0) {
+     color_table = (int*) xmalloc(4 * src_h.used_colors);
+     gbfread(color_table, 1, 4 * src_h.used_colors, f);
+     for (i = 0; i < src_h.used_colors; i++) {
+       int color = color_table[i];
+       /* swap blue and red value */
+       color = (color >> 16) | (color << 16) | (color & 0x00ff00);
+       color_table[i] = color & 0xffffff;
+     }
+   }
+   /* calculate line-size for source and destination */
+   src_line_sz = (src_h.width * src_h.bpp) / 8;
+   src_line_sz = ((int)((src_line_sz + 3) / 4)) * 4;
+   if (src_h.bpp == 24) {
+     dest_bpp = 32;
+   } else {
+     dest_bpp = src_h.bpp;
+   }
+   dest_line_sz = (src_h.width * dest_bpp) / 8;
+   dest_line_sz = ((int)((dest_line_sz + 3) / 4)) * 4;
+   sz = sizeof(*dest_h) + (src_h.height * dest_line_sz);
+   if (src_h.used_colors) {
+     sz += (src_h.used_colors * 4);
+   }
+   ptr = (unsigned char*) xmalloc(sz);
+   dest_h = (gpi_bitmap_header_t*)ptr;
+   *data = ptr;
+   *data_sz = sz;
+   le_write16(&dest_h->index, 0);
+   le_write16(&dest_h->height, src_h.height);
+   le_write16(&dest_h->width, src_h.width);
+   le_write16(&dest_h->line_sz, dest_line_sz);
+   le_write16(&dest_h->bpp, dest_bpp);
+   le_write16(&dest_h->fixed_0, 0);            /* seems to be fixed */
+   le_write32(&dest_h->image_size, dest_line_sz * src_h.height);
+   le_write32(&dest_h->fixed_2c, 0x2c);                /* seems to be fixed */
+   le_write32(&dest_h->flag1, (dest_bpp == 8) ? 0x100 : 0);
+   le_write32(&dest_h->tr_color, 0xff00ff);    /* magenta = transparent color */
+   le_write32(&dest_h->flag2, 0x1);            /* ? enable transparent mode ? */
+   le_write32(&dest_h->size_2c, (dest_line_sz * src_h.height) + 0x2c);
+   /* copy and revert order of BMP lines */
+   ptr = (unsigned char*)dest_h;
+   ptr += (sizeof(*dest_h) + (dest_line_sz * (src_h.height - 1)));
+   gbfseek(f, src_h.image_offset, SEEK_SET);
+   if (src_h.bpp == 24) {
+     /* 24 bpp seems to be not supported, convert to 32 bpp */
+     for (i = 0; i < src_h.height; i++) {
+       int j;
+       unsigned char* p = ptr;
+       for (j = 0; j < src_h.width; j++) {
+         int color;
+         color = (int32_t)gbfgetint16(f) | (gbfgetc(f) << 16);
+         le_write32(p, color);
+         p += 4;
+       }
+       for (j = (src_h.width * src_h.bpp) / 8; j < src_line_sz; j++) {
+         gbfgetc(f);  /* drop fill-in bytes */
+       }
+       ptr -= dest_line_sz;
+     }
+   } else for (i = 0; i < src_h.height; i++) {
+       gbfread(ptr, 1, src_line_sz, f);
+       ptr -= dest_line_sz;
+     }
+   if (src_h.used_colors > 0) {
+     ptr = (unsigned char*)dest_h;
+     ptr += (sizeof(*dest_h) + (src_h.height * src_line_sz));
+     for (i = 0; i < src_h.used_colors; i++) {
+       le_write32(ptr, color_table[i]);
+       ptr += 4;
+     }
+   }
+   if (color_table) {
+     xfree(color_table);
+   }
+   gbfclose(f);
+ }
+ /*******************************************************************************
+ * %%%        global callbacks called by gpsbabel main process              %%% *
+ *******************************************************************************/
+ static void
+ garmin_gpi_rd_init(const char* fname)
+ {
+   fin = gbfopen_le(fname, "rb", MYNAME);
+   rdata = new reader_data_t;
+   read_header();
+   if ((codepage >= 1250) && (codepage <= 1257)) {
+     QString qCodecName = QString("windows-%1").arg(codepage);
+     cet_convert_init(CSTR(qCodecName), 1);
+   } else {
+     fatal(MYNAME ": Unsupported code page (%d). File is likely encrypted.\n", codepage);
+   }
+   units = tolower(opt_units[0]);
+   if ((units != 'm') && (units != 's')) {
+     fatal(MYNAME ": Unknown units parameter (%c).\n", opt_units[0]);
+   }
+ }
+ static void
+ garmin_gpi_wr_init(const char* fname)
+ {
+   int i;
+   if (gpi_timestamp != 0) {                   /* not the first gpi output session */
+     time_t t = time(NULL);
+     if (t <= gpi_timestamp) {
+       gpi_timestamp++;  /* don't create files with same timestamp */
+     } else {
+       gpi_timestamp = t;
+     }
+   } else {
+     gpi_timestamp = gpsbabel_time;  /* always ZERO during 'testo' */
+   }
+   fout = gbfopen_le(fname, "wb", MYNAME);
+   short_h = mkshort_new_handle();
+   setshort_length(short_h, 1024);
+   setshort_badchars(short_h, "\r\n");
+   setshort_mustupper(short_h, 0);
+   setshort_mustuniq(short_h, 1);
+   setshort_whitespace_ok(short_h, 1);
+   setshort_repeating_whitespace_ok(short_h, 0);
+   setshort_defname(short_h, "POI");
+   codepage = 0;
+   for (i = 1250; i <= 1257; i++) {
+     if (QString("windows-%1").arg(i).compare(QString(opt_writecodec), Qt::CaseInsensitive) == 0) {
+       codepage = i;
+       break;
+     }
+   }
+   if (! codepage) {
+     warning(MYNAME ": Unsupported character set (%s)!\n", opt_writecodec);
+     fatal(MYNAME ": Valid values are windows-1250 to windows-1257.\n");
+   }
+   cet_convert_init(opt_writecodec,1);
+   units = tolower(opt_units[0]);
+   if ((units != 'm') && (units != 's')) {
+     fatal(MYNAME ": Unknown units parameter (%c).\n", opt_units[0]);
+   }
+   alerts = (opt_alerts) ? 1 : 0;
+   if (opt_speed) {
+     double scale;
+     alerts = 1;                                       /* Force alerts to be enabled */
+     if (units == 's') {
+       scale = MPH_TO_MPS(1);  /* We need speed in meters per second */
+     } else {
+       scale = KPH_TO_MPS(1);
+     }
+     parse_speed(opt_speed, &defspeed, scale, MYNAME);
+   }
+   if (opt_proximity) {
+     double scale;
+     alerts = 1;                                       /* Force alerts to be enabled */
+     if (units == 's') {
+       scale = MILES_TO_METERS(1);  /* We need proximity in meters */
+     } else {
+       scale = 1000.0;  /* one kilometer in meters */
+     }
+     parse_distance(opt_proximity, &defproximity, scale, MYNAME);
+   }
+   wdata = wdata_alloc();
+ }
+ static void
+ garmin_gpi_rd_deinit(void)
+ {
+   delete rdata;
+   gbfclose(fin);
+ }
+ static void
+ garmin_gpi_wr_deinit(void)
+ {
+   wdata_free(wdata);
+   mkshort_del_handle(&short_h);
+   gbfclose(fout);
+   if ((opt_sleep) && (gpi_timestamp != 0)) {  /* don't sleep during 'testo' */
+     int sleep = atoi(opt_sleep);
+     if (sleep < 1) {
+       sleep = 1;
+     }
+     gpi_timestamp += sleep;
+     while (gpi_timestamp > time(NULL)) {
+       gb_sleep(100);
+     }
+   }
+ }
+ static void
+ garmin_gpi_read(void)
+ {
+   while (1) {
+     int tag = gbfgetint32(fin);
+     if (tag == 0xffff) {
+       return;
+     }
+     if (! read_tag("garmin_gpi_read", tag, NULL)) {
+       return;
+     }
+   };
+ }
+ static void
+ garmin_gpi_write(void)
+ {
+   unsigned char* image;
+   int image_sz;
+   if (strlen(opt_cat) == 0) {
+     fatal(MYNAME ": Can't write empty category!\n");
+   }
+   if (opt_hide_bitmap) {
+     image = NULL;
+     image_sz = 0;
+   } else if (opt_bitmap && *opt_bitmap) {
+     load_bitmap_from_file(opt_bitmap, &image, &image_sz);
+   } else {
+     image = gpi_bitmap;       /* embedded GPSBabel icon in gpi format */
+     image_sz = GPI_BITMAP_SIZE;
+   }
+   waypt_disp_all(enum_waypt_cb);
+   wdata_check(wdata);
+   write_header();
+   write_category(opt_cat, image, image_sz);
+   gbfputint32(0xffff, fout);  /* final tag */
+   gbfputint32(0, fout);               /* ? dummy size ? */
+   if (image != gpi_bitmap) {
+     xfree(image);
+   }
+ }
+ /**************************************************************************/
+ ff_vecs_t garmin_gpi_vecs = {
+   ff_type_file,
+   {
+     (ff_cap)(ff_cap_read | ff_cap_write)      /* waypoints */,
+     ff_cap_none                       /* tracks */,
+     ff_cap_none                       /* routes */
+   },
+   garmin_gpi_rd_init,
+   garmin_gpi_wr_init,
+   garmin_gpi_rd_deinit,
+   garmin_gpi_wr_deinit,
+   garmin_gpi_read,
+   garmin_gpi_write,
+   NULL,
+   garmin_gpi_args,
+   CET_CHARSET_MS_ANSI, 0              /* WIN-CP1252 */
+ };
+ /**************************************************************************/
diff --cc nmea.cc
index 0000000000000000000000000000000000000000,6b221a3138e2a610487f4587fb0134a37b78e791..7cc5fdc140a73b2bb8159194f756e0052e95e109
mode 000000,100644..100644
--- /dev/null
+++ b/nmea.cc
@@@ -1,0 -1,1483 +1,1473 @@@
 -  double latdeg, lngdeg;
 -  double fsec;
 -  char lngdir, latdir;
 -  double hmsd;
 -  int hms;
 -  char valid = 0;
 -  Waypoint* waypt;
 -
+ /*
+       Read files containing selected NMEA 0183 sentences.
+       Based on information by Eino Uikkanenj
+       Copyright (C) 2004-2006 Robert Lipe, robertlipe+source@gpsbabel.org
+       This program is free software; you can redistribute it and/or modify
+       it under the terms of the GNU General Public License as published by
+       the Free Software Foundation; either version 2 of the License, or
+       (at your option) any later version.
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU General Public License for more details.
+       You should have received a copy of the GNU General Public License
+       along with this program; if not, write to the Free Software
+       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+  */
+ #include "defs.h"
+ #include "cet_util.h"
+ #include "gbser.h"
+ #include "strptime.h"
+ #include "jeeps/gpsmath.h"
+ #include <ctype.h>
+ #include <math.h>
+ #include <time.h>
+ #include <stdlib.h>
+ #include <stdio.h>
++#include <QtCore/QStringList>
++  
+ /**********************************************************
+    ' 1      2      3        4 5         6 7 8  9   10   11 12  13 14 15
+    ' $GPGGA - Global Positioning System Fix Data
+    ' $GPGGA,155537,6006.718,N,02426.290,E,1,05,2.4,50.5,M,19.7,M,,*79
+    '  2    123519       Fix taken at 12:35:19 UTC
+    '  3,4  4807.038,N   Latitude 48 deg 07.038' N
+    '  5,6  01131.324,E  Longitude 11 deg 31.324' E
+    '  7    1            Fix quality: 0 = invalid
+    '                                 1 = GPS fix
+    '                                 2 = DGPS fix
+    '  8    08           Number of satellites being tracked
+    '  9    0.9          Horizontal dilution of position
+    ' 10,11 545.4,M      Altitude, Metres, above mean sea level
+    ' 12,13 46.9,M       Height of geoid (mean sea level) above WGS84 ellipsoid
+    ' 14    (empty field) time in seconds since last DGPS update
+    ' 15    (empty field) DGPS station ID number
+    ' $GPWPL - waypoint location
+    ' $GPWPL,4917.16,N,12310.64,W,003*65
+    '  2,3  4917.16,N    Latitude of waypoint
+    '  4,5  12310.64,W   Longitude of waypoint
+    '  6    003          Waypoint ID
+    ' $GPGLL - Geographic position, Latitude and Longitude
+    ' $GPGLL,4916.45,N,12311.12,W,225444,A
+    '  2,3  4916.46,N    Latitude 49 deg. 16.45 min. North
+    '  4,5  12311.12,W   Longitude 123 deg. 11.12 min. West
+    '  6    225444       Fix taken at 22:54:44 UTC
+    '  7    A            Data valid
+    ' $GPRMC - Recommended minimum specific GNSS Data
+    ' $GPRMC,085721.194,A,5917.7210,N,01103.9227,E,21.42,50.33,300504,,*07
+    '  2    085721       Fix taken at 08:57:21 UTC
+    '  3    A                          Fix valid (this field reads V if fix is not valid)
+    '  4,5  5917.7210,N   Latitude 59 deg 17.7210' N
+    '  6,7  01103.9227,E  Longitude 11 deg 03.9227' E
+    '  8    21.42                      Speed over ground (knots)
+    '  9    50.33                      Course over ground (true)
+    '  10   300504                     Date 30/05-2004
+    '  11   Empty field        Magnetic variation
+         GSA - GPS DOP and active satellites
+         $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
+              A            Auto selection of 2D or 3D fix (M = manual)
+              3            3D fix
+              04,05...     PRNs of satellites used for fix (space for 12)
+              2.5          PDOP (dilution of precision)
+              1.3          Horizontal dilution of precision (HDOP)
+              2.1          Vertical dilution of precision (VDOP)
+                DOP is an indication of the effect of satellite geometry on
+                the accuracy of the fix.
+         VTG - Track made good and ground speed
+         $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K
+              054.7,T      True track made good
+              034.4,M      Magnetic track made good
+              005.5,N      Ground speed, knots
+              010.2,K      Ground speed, Kilometers per hour
+         WPL - waypoint location
+         $GPWPL,4917.16,N,12310.64,W,003*65
+              4917.16,N    Latitude of waypoint
+              12310.64,W   Longitude of waypoint
+              003          Waypoint ID
+                When a route is active, this sentence is sent once for each
+                waypoint in the route, in sequence. When all waypoints have
+                been reported, GPR00 is sent in the next data set. In any
+                group of sentences, only one WPL sentence, or an R00
+                sentence, will be sent.
+    ' The optional checksum field consists of a "*" and two hex digits repre-
+    ' senting the exclusive OR of all characters between, but not including,
+    ' the "$" and "*".  A checksum is required on some sentences.
+ ****************************************/
+ /*
+  * An input file may have both GGA and GLL and RMC sentences for the exact
+  * same position fix. If we see a single GGA, start ignoring GLL's and RMC's.
+  *    GLL's will also be ignored if RMC's are found and GGA's not found.
+  */
+ /*
+ Zmarties notes:
+ In practice, all fields of the NMEA sentences should be treated as optional -
+ if the data is not available, then the field can be omitted (hence leading
+ to the output of two consecutive commas).
+ An NMEA recording can start anywhere in the stream of data.  It is therefore
+ necessary to discard sentences until sufficient data has been processed to
+ have all the necessary data to construct a waypoint.  In practice, this means
+ discarding data until we have had the first sentence that provides the date.
+ (We could scan forwards in the stream of data to find the first date, and
+ then back apply it to all previous sentences, but that is probably more
+ complexity that is necessary - the lost of one waypoint at the start of the
+ stream can normally be tolerated.)
+ If a sentence is received without a checksum, but previous sentences have
+ had checksums, it is best to discard that sentence.  In practice, the only
+ time I have seen this is when the recording stops suddenly, where the last
+ sentence is truncated - and missing part of the line, including the checksum.
+ */
+ typedef enum {
+   gp_unknown = 0,
+   gpgga,
+   gplgll,
+   gprmc
+ } preferred_posn_type;
+ static enum {
+   rm_unknown = 0,
+   rm_serial,
+   rm_file
+ } read_mode;
+ static gbfile* file_in, *file_out;
+ static route_head* trk_head;
+ static short_handle mkshort_handle;
+ static preferred_posn_type posn_type;
+ static struct tm tm;
+ static Waypoint* curr_waypt;
+ static Waypoint* last_waypt;
+ static void* gbser_handle;
+ static const char* posn_fname;
+ static queue pcmpt_head;
+ static int without_date;      /* number of created trackpoints without a valid date */
+ static struct tm opt_tm;      /* converted "date" parameter */
+ #define MYNAME "nmea"
+ static char* opt_gprmc;
+ static char* opt_gpgga;
+ static char* opt_gpvtg;
+ static char* opt_gpgsa;
+ static char* snlenopt;
+ static char* optdate;
+ static char* getposnarg;
+ static char* opt_sleep;
+ static char* opt_baud;
+ static char* opt_append;
+ static char* opt_gisteq;
+ static char* opt_ignorefix;
+ static long sleepus;
+ static int getposn;
+ static int append_output;
+ static int amod_waypoint;
+ static time_t last_time;
+ static double last_read_time;   /* Last timestamp of GGA or PRMC */
+ static int datum;
+ static int had_checksum;
+ static Waypoint* nmea_rd_posn(posn_status*);
+ static void nmea_rd_posn_init(const char* fname);
+ arglist_t nmea_args[] = {
+   {"snlen", &snlenopt, "Max length of waypoint name to write", "6", ARGTYPE_INT, "1", "64" },
+   {"gprmc", &opt_gprmc, "Read/write GPRMC sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+   {"gpgga", &opt_gpgga, "Read/write GPGGA sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+   {"gpvtg", &opt_gpvtg, "Read/write GPVTG sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+   {"gpgsa", &opt_gpgsa, "Read/write GPGSA sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+   {"date", &optdate, "Complete date-free tracks with given date (YYYYMMDD).", NULL, ARGTYPE_INT, ARG_NOMINMAX },
+   {
+     "get_posn", &getposnarg, "Return current position as a waypoint",
+     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+   },
+   {"pause", &opt_sleep, "Decimal seconds to pause between groups of strings", NULL, ARGTYPE_INT, ARG_NOMINMAX },
+   {"append_positioning", &opt_append, "Append realtime positioning data to the output file instead of truncating", "0", ARGTYPE_BOOL, ARG_NOMINMAX },
+   {"baud", &opt_baud, "Speed in bits per second of serial port (baud=4800)", NULL, ARGTYPE_INT, ARG_NOMINMAX },
+   {"gisteq", &opt_gisteq, "Write tracks for Gisteq Phototracker", "0", ARGTYPE_BOOL, ARG_NOMINMAX },
+   {"ignore_fix", &opt_ignorefix, "Accept position fixes in gpgga marked invalid", "0", ARGTYPE_BOOL, ARG_NOMINMAX },
+   ARG_TERMINATOR
+ };
+ #define CHECK_BOOL(a) if (a && (*a == '0')) a = NULL
+ /*
+  * Slightly different than the Magellan checksum fn.
+  */
+ int
+ nmea_cksum(const char* const buf)
+ {
+   int x = 0 ;
+   const char* p;
+   for (p = buf; *p; p++) {
+     x ^= *p;
+   }
+   return x;
+ }
+ static void
+ nmea_add_wpt(Waypoint* wpt, route_head* trk)
+ {
+   if (datum != DATUM_WGS84) {
+     double lat, lon, alt;
+     GPS_Math_Known_Datum_To_WGS84_M(
+       wpt->latitude, wpt->longitude, 0,
+       &lat, &lon, &alt, datum);
+     wpt->latitude = lat;
+     wpt->longitude = lon;
+   }
+   if (trk != NULL) {
+     track_add_wpt(trk, wpt);
+   } else {
+     waypt_add(wpt);
+   }
+ }
+ static void
+ nmea_release_wpt(Waypoint* wpt)
+ {
+   if (wpt && ((wpt->Q.next == NULL) || (wpt->Q.next == &wpt->Q))) {
+     /* This waypoint isn't queued.
+        Release it, because we don't have any reference to this
+        waypoint (! memory leak !) */
+     delete wpt;
+   }
+ }
+ static void
+ nmea_rd_init(const char* fname)
+ {
+   curr_waypt = NULL;
+   last_waypt = NULL;
+   last_time = -1;
+   datum = DATUM_WGS84;
+   had_checksum = 0;
+   CHECK_BOOL(opt_gprmc);
+   CHECK_BOOL(opt_gpgga);
+   CHECK_BOOL(opt_gpvtg);
+   CHECK_BOOL(opt_gpgsa);
+   CHECK_BOOL(opt_gisteq);
+   QUEUE_INIT(&pcmpt_head);
+   if (getposnarg) {
+     getposn = 1;
+   }
+   /* A special case hack that gets our current position and returns
+    * it as one waypoint.
+    */
+   if (getposn) {
+     Waypoint* wpt;
+     posn_status st;
+     nmea_rd_posn_init(fname);
+     wpt = nmea_rd_posn(&st);
+     if (!wpt) {
+       return;
+     }
+     wpt->shortname = "Position";
+     nmea_add_wpt(wpt, NULL);
+     return;
+   }
+   read_mode = rm_file;
+   file_in = gbfopen(fname, "rb", MYNAME);
+ }
+ static  void
+ nmea_rd_deinit(void)
+ {
+   switch (read_mode) {
+   case rm_serial:
+     gbser_deinit(gbser_handle);
+     break;
+   case rm_file:
+     gbfclose(file_in);
+     file_in = NULL;
+     break;
+   default:
+     fatal("nmea_rd_deinit: illegal read_mode.\n");
+     break;
+   }
+ }
+ static void
+ nmea_wr_init(const char* portname)
+ {
+   CHECK_BOOL(opt_gprmc);
+   CHECK_BOOL(opt_gpgga);
+   CHECK_BOOL(opt_gpvtg);
+   CHECK_BOOL(opt_gpgsa);
+   CHECK_BOOL(opt_gisteq);
+   append_output = atoi(opt_append);
+   file_out = gbfopen(portname, append_output ? "a+" : "w+", MYNAME);
+   sleepus = -1;
+   if (opt_sleep) {
+     if (*opt_sleep) {
+       sleepus = 1e6 * atof(opt_sleep);
+     } else {
+       sleepus = -1;
+     }
+   }
+   mkshort_handle = mkshort_new_handle();
+   setshort_length(mkshort_handle, atoi(snlenopt));
+   if (opt_gisteq) {
+     opt_gpgga = NULL;
+     opt_gpvtg = NULL;
+     opt_gpgsa = NULL;
+   }
+ }
+ static  void
+ nmea_wr_deinit(void)
+ {
+   gbfclose(file_out);
+   mkshort_del_handle(&mkshort_handle);
+ }
+ static void
+ nmea_set_waypoint_time(Waypoint* wpt, struct tm* time, double fsec)
+ {
+   if (time->tm_year == 0) {
+     wpt->SetCreationTime(((((time_t)time->tm_hour * 60) + time->tm_min) * 60) + time->tm_sec, lround(1000.0 * fsec));
+     if (wpt->wpt_flags.fmt_use == 0) {
+       wpt->wpt_flags.fmt_use = 1;
+       without_date++;
+     }
+   } else {
+     wpt->SetCreationTime(mkgmtime(time), lround(1000.0 * fsec));
+     if (wpt->wpt_flags.fmt_use != 0) {
+       wpt->wpt_flags.fmt_use = 0;
+       without_date--;
+     }
+   }
+ }
+ static void
+ gpgll_parse(char* ibuf)
+ {
 -  sscanf(ibuf,"$%*2cGLL,%lf,%c,%lf,%c,%lf,%c,",
 -         &latdeg,&latdir,
 -         &lngdeg,&lngdir,
 -         &hmsd,&valid);
+   if (trk_head == NULL) {
+     trk_head = route_head_alloc();
+     track_add_head(trk_head);
+   }
 -  if (valid != 'A') {
++  QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
 -  hms = (int) hmsd;
++  double latdeg = 0;
++  if (fields.size() > 1) fields[1].toDouble();
++  QChar latdir = 'N';
++  if (fields.size() > 2) latdir = fields[2][0];
++  double lngdeg = 0;
++  if (fields.size() > 3) lngdeg = fields[3].toDouble();
++  QChar lngdir = 'E';
++  if (fields.size() > 4) lngdir = fields[4][0];
++  double hmsd = 0;
++  if (fields.size() > 5) hmsd = fields[5].toDouble();
++  bool valid = false;
++  if (fields.size() > 6) valid = fields[6].startsWith('A');
++
++  if (!valid) {
+     return;
+   }
 -  fsec = hmsd - hms;
++  int hms = (int) hmsd;
+   last_read_time = hms;
 -  waypt = new Waypoint;
++  double fsec = hmsd - hms;
+   tm.tm_sec = hms % 100;
+   hms = hms / 100;
+   tm.tm_min = hms % 100;
+   hms = hms / 100;
+   tm.tm_hour = hms % 100;
 -  double latdeg, lngdeg;
 -  char lngdir, latdir;
 -  double hms;
 -  double alt;
 -  int fix = fix_unknown;
 -  int nsats = 0;
 -  double hdop;
 -  char altunits;
 -  double geoidheight;
 -  char geoidheightunits;
 -  Waypoint* waypt;
 -  double fsec;
 -
++  Waypoint* waypt = new Waypoint;
+   nmea_set_waypoint_time(waypt, &tm, fsec);
+   if (latdir == 'S') {
+     latdeg = -latdeg;
+   }
+   waypt->latitude = ddmm2degrees(latdeg);
+   if (lngdir == 'W') {
+     lngdeg = -lngdeg;
+   }
+   waypt->longitude = ddmm2degrees(lngdeg);
+   nmea_release_wpt(curr_waypt);
+   curr_waypt = waypt;
+ }
+ static void
+ gpgga_parse(char* ibuf)
+ {
 -  sscanf(ibuf,"$%*2cGGA,%lf,%lf,%c,%lf,%c,%d,%d,%lf,%lf,%c,%lf,%c",
 -         &hms, &latdeg,&latdir,
 -         &lngdeg,&lngdir,
 -         &fix,&nsats,&hdop,&alt,&altunits,&geoidheight,&geoidheightunits);
+   if (trk_head == NULL) {
+     trk_head = route_head_alloc();
+     track_add_head(trk_head);
+   }
 -  fsec = hms - (int)hms;
++  QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
++  double hms = 0;
++  if (fields.size() > 1) hms = fields[1].toDouble();
++  double latdeg = 0;
++  if (fields.size() > 2) latdeg = fields[2].toDouble();
++  QChar latdir = 'N';
++  if (fields.size() > 3) latdir = fields[3][0];
++  double lngdeg = 0;
++  if (fields.size() > 4) lngdeg = fields[4].toDouble();
++  QChar lngdir = 'W';
++  if (fields.size() > 5) lngdir = fields[5][0];
++  int fix = fix_unknown;
++  if (fields.size() > 6) fix = fields[6].toInt();
++  int nsats = 0;
++  if (fields.size() > 7) nsats = fields[7].toInt();
++  double hdop = 0;
++  if (fields.size() > 8) hdop = fields[8].toDouble();
++  double alt = unknown_alt;
++  if (fields.size() > 9) alt = fields[9].toDouble();
++  QChar altunits;
++  if (fields.size() > 10) altunits = fields[10][0];
++  double geoidheight = unknown_alt;
++  if (fields.size() > 11) geoidheight = fields[11].toDouble();
++  QChar geoidheightunits = 'M';
++  if (fields.size() > 12) geoidheightunits = fields[12][0];
+   /*
+    * In serial mode, allow the fix with an invalid position through
+    * as serial units will often spit a remembered position up and
+    * that is more comfortable than nothing at all...
+    */
+   CHECK_BOOL(opt_ignorefix);
+   if ((fix <= 0) && (read_mode != rm_serial) && (!opt_ignorefix)) {
+     return;
+   }
+   last_read_time = hms;
 -  waypt  = new Waypoint;
++  double fsec = hms - (int)hms;
+   tm.tm_sec = (long) hms % 100;
+   hms = hms / 100;
+   tm.tm_min = (long) hms % 100;
+   hms = hms / 100;
+   tm.tm_hour = (long) hms % 100;
 -  double latdeg, lngdeg;
 -  char lngdir, latdir;
 -  double hms;
 -  char fix;
 -  unsigned int dmy;
 -  double speed,course;
 -  Waypoint* waypt;
 -  double fsec;
 -  char* dmybuf;
 -  int i;
 -
++  Waypoint* waypt = new Waypoint;
+   nmea_set_waypoint_time(waypt, &tm, fsec);
+   if (latdir == 'S') {
+     latdeg = -latdeg;
+   }
+   waypt->latitude = ddmm2degrees(latdeg);
+   if (lngdir == 'W') {
+     lngdeg = -lngdeg;
+   }
+   waypt->longitude = ddmm2degrees(lngdeg);
+   waypt->altitude = alt;
+   WAYPT_SET(waypt, geoidheight, geoidheight);
+   waypt->sat  = nsats;
+   waypt->hdop         = hdop;
+   switch (fix) {
+   case 0:
+     waypt->fix = fix_none;
+     break;
+   case 1:
+     waypt->fix  = (nsats>3)?(fix_3d):(fix_2d);
+     break;
+   case 2:
+     waypt->fix = fix_dgps;
+     break;
+   case 3:
+     waypt->fix = fix_pps;
+     break;
+   }
+   nmea_release_wpt(curr_waypt);
+   curr_waypt = waypt;
+ }
+ static void
+ gprmc_parse(char* ibuf)
+ {
 -  /*
 -   * Read everything except the dmy, in case lngdeg
 -   * and lngdir are missing.
 -   */
 -  sscanf(ibuf,"$%*2cRMC,%lf,%c,%lf,%c,%lf,%c,%lf,%lf",
 -         &hms, &fix, &latdeg, &latdir,
 -         &lngdeg, &lngdir,
 -         &speed, &course);
+   if (trk_head == NULL) {
+     trk_head = route_head_alloc();
+     track_add_head(trk_head);
+   }
 -  /* Skip past nine commas in ibuf to reach the dmy value */
 -  for (dmybuf=ibuf,i=0; i<9; i++) {
 -    dmybuf= strchr(dmybuf, ',');
 -    if (dmybuf==NULL) {
 -      /* If we run out of commas, the sentence is invalid. */
 -      return;
 -    }
 -    dmybuf++;
 -  }
 -
 -  /* Now read dmy from the correct position */
 -  sscanf(dmybuf,"%u", &dmy);
 -
++  QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
++  double hms = 0;
++  if (fields.size() > 1) hms = fields[1].toDouble();
++  QChar fix = 'V'; // V == "Invalid"
++  if (fields.size() > 2) fix = fields[2][0];
++  double latdeg = 0;
++  if (fields.size() > 3) latdeg = fields[3].toDouble();
++  QChar latdir = 'N';
++  if (fields.size() > 4) latdir = fields[4][0];
++  double lngdeg = 0;
++  if (fields.size() > 5) lngdeg = fields[5].toDouble();
++  QChar lngdir = 'W';
++  if (fields.size() > 6) lngdir = fields[6][0];
++  double speed = 0;
++  if (fields.size() > 7) speed = fields[7].toDouble();
++  double course = 0;
++  if (fields.size() > 8) course = fields[8].toDouble();
++  int dmy = 0;
++  if (fields.size() > 9) dmy = fields[9].toDouble();
+   if (fix != 'A') {
+     /* ignore this fix - it is invalid */
+     return;
+   }
 -  fsec = hms - (int)hms;
+   last_read_time = hms;
 -  waypt  = new Waypoint;
++  double fsec = hms - (int)hms;
+   tm.tm_sec = (long) hms % 100;
+   hms = hms / 100;
+   tm.tm_min = (long) hms % 100;
+   hms = hms / 100;
+   tm.tm_hour = (long) hms % 100;
+   tm.tm_year = dmy % 100 + 100;
+   dmy = dmy / 100;
+   tm.tm_mon  = dmy % 100 - 1;
+   dmy = dmy / 100;
+   tm.tm_mday = dmy;
+   if (posn_type == gpgga) {
+     /* capture useful data update and exit */
+     if (curr_waypt) {
+       if (! WAYPT_HAS(curr_waypt, speed)) {
+         WAYPT_SET(curr_waypt, speed, KNOTS_TO_MPS(speed));
+       }
+       if (! WAYPT_HAS(curr_waypt, course)) {
+         WAYPT_SET(curr_waypt, course, course);
+       }
+       /* The change of date wasn't recorded when
+        * going from 235959 to 000000. */
+       nmea_set_waypoint_time(curr_waypt, &tm, fsec);
+     }
+     /* This point is both a waypoint and a trackpoint. */
+     if (amod_waypoint) {
+       waypt_add(new Waypoint(*curr_waypt));
+       amod_waypoint = 0;
+     }
+     return;
+   }
 -
++  Waypoint* waypt = new Waypoint;
+   WAYPT_SET(waypt, speed, KNOTS_TO_MPS(speed));
 -  Waypoint* waypt;
+   WAYPT_SET(waypt, course, course);
+   nmea_set_waypoint_time(waypt, &tm, fsec);
+   if (latdir == 'S') {
+     latdeg = -latdeg;
+   }
+   waypt->latitude = ddmm2degrees(latdeg);
+   if (lngdir == 'W') {
+     lngdeg = -lngdeg;
+   }
+   waypt->longitude = ddmm2degrees(lngdeg);
+   nmea_release_wpt(curr_waypt);
+   curr_waypt = waypt;
+   /* This point is both a waypoint and a trackpoint. */
+   if (amod_waypoint) {
+     waypt_add(new Waypoint(*waypt));
+     amod_waypoint = 0;
+   }
+ }
+ static void
+ gpwpl_parse(char* ibuf)
+ {
 -  waypt  = new Waypoint;
+   double latdeg, lngdeg;
+   char latdir, lngdir;
+   char sname[99];
+   sscanf(ibuf,"$%*2cWPL,%lf,%c,%lf,%c,%98[^*]",
+          &latdeg,&latdir,
+          &lngdeg,&lngdir,
+          sname);
 -  float       course;
 -  char        ct;
 -  float       magcourse;
 -  char        cm;
 -  double      speed_n;
 -  char        cn;
 -  double      speed_k;
 -  char        ck;
 -
 -  sscanf(ibuf,"$%*2cVTG,%f,%c,%f,%c,%lf,%c,%lf,%c",
 -         &course,&ct,&magcourse,&cm,&speed_n,&cn,&speed_k,&ck);
++  Waypoint* waypt = new Waypoint;
+   if (latdir == 'S') {
+     latdeg = -latdeg;
+   }
+   waypt->latitude = ddmm2degrees(latdeg);
+   if (lngdir == 'W') {
+     lngdeg = -lngdeg;
+   }
+   waypt->longitude = ddmm2degrees(lngdeg);
+   waypt->shortname = sname;
+   curr_waypt = NULL; /* waypoints won't be updated with GPS fixes */
+   nmea_add_wpt(waypt, NULL);
+ }
+ static void
+ gpzda_parse(char* ibuf)
+ {
+   double hms;
+   int dd, mm, yy, lclhrs, lclmins;
+   sscanf(ibuf,"$%*2cZDA,%lf,%d,%d,%d,%d,%d",
+          &hms, &dd, &mm, &yy, &lclhrs, &lclmins);
+   tm.tm_sec  = (int) hms % 100;
+   tm.tm_min  = (((int) hms - tm.tm_sec) / 100) % 100;
+   tm.tm_hour = (int) hms / 10000;
+   tm.tm_mday = dd;
+   tm.tm_mon  = mm - 1;
+   tm.tm_year = yy - 1900;
++  // FIXME: why do we do all this and then do nothing with the result?
++  // This can't have worked.
+ }
+ static void
+ gpgsa_parse(char* ibuf)
+ {
+   char fixauto;
+   char fix;
+   int  prn[12] = {0};
+   int  scn,cnt;
+   float pdop=0,hdop=0,vdop=0;
+   char*       tok=0;
+   memset(prn,0xff,sizeof(prn));
+   scn = sscanf(ibuf,"$%*2cGSA,%c,%c,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+                &fixauto, &fix,
+                &prn[0],&prn[1],&prn[2],&prn[3],&prn[4],&prn[5],
+                &prn[6],&prn[7],&prn[8],&prn[9],&prn[10],&prn[11]);
+   if (scn < 4) {
+     warning(MYNAME ": Short GSA sentence.");
+   }
+   /*
+       sscanf has scanned all the leftmost elements
+       we'll rescan by skipping 15 commas to the dops
+   */
+   tok = ibuf;
+   for (cnt=0; (tok)&&(cnt<15); cnt++) {
+     tok = strchr(tok,',');
+     if (!tok) {
+       break;
+     }
+     tok++;
+   }
+   if (tok) {
+     sscanf(tok,"%f,%f,%f",&pdop,&hdop,&vdop);
+   }
+   if (curr_waypt) {
+     if (curr_waypt->fix!=fix_dgps) {
+       if      (fix=='3')      {
+         curr_waypt->fix=fix_3d;
+       } else if (fix=='2')    {
+         curr_waypt->fix=fix_2d;
+       }
+     }
+     curr_waypt->pdop = pdop;
+     curr_waypt->hdop = hdop;
+     curr_waypt->vdop = vdop;
+     if (curr_waypt->sat  <= 0)        {
+       for (cnt=0; cnt<12; cnt++) {
+         curr_waypt->sat += (prn[cnt]>0)?(1):(0);
+       }
+     }
+   }
+ }
+ static void
+ gpvtg_parse(char* ibuf)
+ {
 -
 -    if (speed_k>0)
++  QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
++  double course = 0;
++  if (fields.size() > 1) course = fields[1].toDouble();
++  double speed_n = 0;
++  if (fields.size() > 5) speed_n = fields[5].toDouble();
++  double speed_k = 0;
++  if (fields.size() > 7) speed_k = fields[7].toDouble();
+   if (curr_waypt) {
+     WAYPT_SET(curr_waypt, course, course);
 -      else {
 -        WAYPT_SET(curr_waypt, speed, KNOTS_TO_MPS(speed_n));
 -      }
 -
++    if (speed_k > 0) {
+       WAYPT_SET(curr_waypt, speed, KPH_TO_MPS(speed_k))
 -  int deg;
 -  double minutes;
 -
 -  deg = d  / 100000;
 -  minutes = (((d / 100000.0) - deg) * 100) / 60.0;
++    } else {
++      WAYPT_SET(curr_waypt, speed, KNOTS_TO_MPS(speed_n));
++    }
+   }
+ }
+ /*
+  *  AVMAP EKP-IV Tracks - a proprietary (and very weird) extended NMEA.
+  * https://sourceforge.net/tracker/?func=detail&atid=489478&aid=1640814&group_id=58972
+  */
+ static
+ double pcmpt_deg(int d)
+ {
++  int deg = d  / 100000;
++  double minutes = (((d / 100000.0) - deg) * 100) / 60.0;
+   return (double) deg + minutes;
+ }
+ void
+ pcmpt_parse(char* ibuf)
+ {
+   int i, j1, j2, j3, j4, j5, j6;
+   int lat, lon;
+   char altflag, u1, u2;
+   float alt, f1, f2;
+   char coords[20] = {0};
+   int dmy, hms;
+   dmy = hms = 0;
+   sscanf(ibuf,"$PCMPT,%d,%d,%d,%c,%f,%d,%19[^,],%d,%f,%d,%f,%c,%d,%c,%d",
+          &j1, &j2, &j3, &altflag, &alt, &j4, (char*) &coords,
+          &j5, &f1, &j6, &f2, &u1, &dmy, &u2, &hms);
+   if (altflag == 'D' && curr_waypt && alt > 0) {
+     curr_waypt->altitude =  alt /*+ 500*/;
+     return;
+   }
+   /*
+    * There are a couple of different second line records, but we
+    * don't care about them.
+    */
+   if (j2 != 1) {
+     return;
+   }
+   sscanf(coords, "%d%n", &lat, &i);
+   if (coords[i] == 'S') {
+     lat = -lat;
+   }
+   sscanf(coords + i + 1, "%d%n", &lon, &i);
+   if (coords[i] == 'W') {
+     lon= -lon;
+   }
+   if (lat || lon) {
+     curr_waypt = new Waypoint;
+     curr_waypt->longitude = pcmpt_deg(lon);
+     curr_waypt->latitude = pcmpt_deg(lat);
+     tm.tm_sec = (long) hms % 100;
+     hms = hms / 100;
+     tm.tm_min = (long) hms % 100;
+     hms = hms / 100;
+     tm.tm_hour = (long) hms % 100;
+     tm.tm_year = dmy % 10000 - 1900;
+     dmy = dmy / 10000;
+     tm.tm_mon  = dmy % 100 - 1;
+     dmy = dmy / 100;
+     tm.tm_mday = dmy;
+     nmea_set_waypoint_time(curr_waypt, &tm, 0);
+     ENQUEUE_HEAD(&pcmpt_head, &curr_waypt->Q);
+   } else {
+     queue* elem, *tmp;
+     route_head* trk_head;
+     if (QUEUE_EMPTY(&pcmpt_head)) {
+       return;
+     }
+     /*
+      * Since we oh-so-cleverly inserted points at the head,
+      * we can rip through the queue forward now to get our
+     `          * handy-dandy reversing effect.
+      */
+     trk_head = route_head_alloc();
+     track_add_head(trk_head);
+     QUEUE_FOR_EACH(&pcmpt_head, elem, tmp) {
+       Waypoint* wpt = (Waypoint*) dequeue(elem);
+       nmea_add_wpt(wpt, trk_head);
+     }
+   }
+ }
+ static void
+ nmea_fix_timestamps(route_head* track)
+ {
+   if ((trk_head == NULL) || (without_date == 0)) {
+     return;
+   }
+   if (tm.tm_year == 0) {
+     queue* elem, *temp;
+     Waypoint* prev = NULL;
+     time_t delta_tm;
+     if (optdate == NULL) {
+       warning(MYNAME ": No date found within track (all points dropped)!\n");
+       warning(MYNAME ": Please use option \"date\" to preset a valid date for thoose tracks.\n");
+       track_del_head(track);
+       return;
+     }
+     delta_tm = mkgmtime(&opt_tm);
+     QUEUE_FOR_EACH(&track->waypoint_list, elem, temp) {
+       Waypoint* wpt = (Waypoint*)elem;
+       wpt->creation_time += delta_tm;
+       if ((prev != NULL) && (prev->creation_time > wpt->creation_time)) {     /* go over midnight ? */
+         delta_tm += SECONDS_PER_DAY;
+         wpt->creation_time += SECONDS_PER_DAY;
+       }
+       prev = wpt;
+     }
+   } else {
+     time_t prev;
+     queue* elem;
+     tm.tm_hour = 23;  /* last date found */
+     tm.tm_min = 59;
+     tm.tm_sec = 59;
+     prev = mkgmtime(&tm);
+     /* go backward through the track and complete timestamps */
+     for (elem = QUEUE_LAST(&track->waypoint_list); elem != &track->waypoint_list; elem=elem->prev) {
+       Waypoint* wpt = (Waypoint*)elem;
+       if (wpt->wpt_flags.fmt_use != 0) {
+         time_t dt;
+         wpt->wpt_flags.fmt_use = 0;   /* reset flag */
+         dt = (prev / SECONDS_PER_DAY) * SECONDS_PER_DAY;
+         wpt->creation_time += dt;
+         if (wpt->creation_time.toTime_t() > prev) {
+           wpt->creation_time+=SECONDS_PER_DAY;
+         }
+       }
+       prev = wpt->GetCreationTime().toTime_t();
+     }
+   }
+ }
+ static int
+ notalkerid_strmatch(const char * s1, const char *sentenceFormatterMnemonicCode)
+ {
+ /*
+  * compare leading start of parametric sentence character ('$'), sentence address field, and trailing comma
+  * to the desired sentence formatter mneumonic code (the 3rd-5th characters of the sentence address field).
+  * The talker identifier mneumonic (the 1st-2nd characters of the sentence address field)
+  * is likely "GP" for Global Posilioning System (GPS)
+  * but other talkers like "IN" for Integrated Navigation can emit relevant sentences,
+  * so we ignore the talker identifier mneumonic.
+  */
+ return strncmp(s1,"$",1) || strncmp(s1+3,sentenceFormatterMnemonicCode,3) || strncmp(s1+6,",",1);
+ }
+ void
+ nmea_parse_one_line(char* ibuf)
+ {
+   char* ck;
+   int ckval, ckcmp;
+   char* tbuf = lrtrim(ibuf);
+   /*
+    * GISTEQ PhotoTracker (stupidly) puts a bogus field in front
+    * of the line.  Look for it and toss it.
+    */
+   if (0 == strncmp(tbuf, "---,", 4)) {
+     tbuf += 4;
+   }
+   if (*tbuf != '$') {
+     return;
+   }
+   ck = strrchr(tbuf, '*');
+   if (ck != NULL) {
+     *ck = '\0';
+     ckval = nmea_cksum(&tbuf[1]);
+     *ck = '*';
+     ck++;
+     sscanf(ck, "%2X", &ckcmp);
+     if (ckval != ckcmp) {
+ #if 0
+       printf("ckval %X, %X, %s\n", ckval, ckcmp, ck);
+       printf("NMEA %s\n", tbuf);
+ #endif
+       return;
+     }
+     had_checksum = 1;
+   } else if (had_checksum) {
+     /* we have had a checksum on all previous sentences, but not on this
+     one, which probably indicates this line is truncated */
+     had_checksum = 0;
+     return;
+   }
+   if (strstr(tbuf+1,"$")!=NULL) {
+     /* If line has more than one $, there is probably an error in it. */
+     return;
+   }
+   /* @@@ zmarties: The parse routines all assume all fields are present, but
+      the NMEA format allows any field to be missed out if there is no data
+      for that field.  Rather than change all the parse routines, we first
+      substitute a default value of zero for any missing field.
+   */
+   if (strstr(tbuf, ",,")) {
+     tbuf = gstrsub(tbuf, ",,", ",0,");
+   }
+   if (0 == notalkerid_strmatch(tbuf, "WPL")) {
+     gpwpl_parse(tbuf);
+   } else if (opt_gpgga && (0 == notalkerid_strmatch(tbuf, "GGA"))) {
+     posn_type = gpgga;
+     gpgga_parse(tbuf);
+   } else if (opt_gprmc && (0 == notalkerid_strmatch(tbuf, "RMC"))) {
+     if (posn_type != gpgga) {
+       posn_type = gprmc;
+     }
+     /*
+      * Always call gprmc_parse() because like GPZDA
+      * it contains the full date.
+      */
+     gprmc_parse(tbuf);
+   } else if (0 == notalkerid_strmatch(tbuf, "GLL")) {
+     if ((posn_type != gpgga) && (posn_type != gprmc)) {
+       gpgll_parse(tbuf);
+     }
+   } else if (0 == notalkerid_strmatch(tbuf, "ZDA")) {
+     gpzda_parse(tbuf);
+   } else if (0 == strncmp(tbuf, "$PCMPT,", 7)) {
+     pcmpt_parse(tbuf);
+   } else if (opt_gpvtg && (0 == notalkerid_strmatch(tbuf, "VTG"))) {
+     gpvtg_parse(tbuf); /* speed and course */
+   } else if (opt_gpgsa && (0 == notalkerid_strmatch(tbuf, "GSA"))) {
+     gpgsa_parse(tbuf); /* GPS fix */
+   } else if (0 == strncmp(tbuf, "$ADPMB,5,0", 10)) {
+     amod_waypoint = 1;
+   }
+   if (tbuf != ibuf) {
+     /* clear up the dynamic buffer we used because substition was required */
+     xfree(tbuf);
+   }
+ }
+ static void
+ nmea_read(void)
+ {
+   char* ibuf;
+   char* ck;
+   double lt = -1;
+   int line = -1;
+   posn_type = gp_unknown;
+   trk_head = NULL;
+   without_date = 0;
+   memset(&tm, 0, sizeof(tm));
+   opt_tm = tm;
+   /* This was done in rd_init() */
+   if (getposn) {
+     return;
+   }
+   if (optdate) {
+     memset(&opt_tm, 0, sizeof(opt_tm));
+     ck = (char*)strptime(optdate, "%Y%m%d", &opt_tm);
+     if ((ck == NULL) || (*ck != '\0') || (strlen(optdate) != 8)) {
+       fatal(MYNAME ": Invalid date \"%s\"!\n", optdate);
+     } else if (opt_tm.tm_year < 70) {
+       fatal(MYNAME ": Date \"%s\" is out of range (have to be 19700101 or later)!\n", optdate);
+     }
+   }
+   curr_waypt = NULL;
+   while ((ibuf = gbfgetstr(file_in))) {
+     char* sdatum, *cx;
+     line++;
+     if ((line == 0) & file_in->unicode) {
+       cet_convert_init(CET_CHARSET_UTF8, 1);
+     }
+     if ((line == 0) && (case_ignore_strncmp(ibuf, "@SonyGPS/ver", 12) == 0)) {
+       /* special hack for Sony GPS-CS1 files:
+          they are fully (?) nmea compatible, but come with a header line like
+          "@Sonygps/ver1.0/wgs-84". */
+       /* The Sony GPS-CS3KA extends that line even further
+          so we now look for the second field to be /
+          delimited.
+          @Sonygps/ver1.0/wgs-84/gps-cs3.0
+        */
+       /* Check the GPS datum */
+       cx = strchr(&ibuf[12], '/');
+       if (cx != NULL) {
+         char* edatum;
+         sdatum = cx + 1;
+         edatum = strchr(sdatum, '/');
+         if (edatum) {
+           *edatum = 0;
+         }
+         datum = GPS_Lookup_Datum_Index(sdatum);
+         if (datum < 0) {
+           fatal(MYNAME "/SonyGPS: Unsupported datum \"%s\" in source data!\n", sdatum);
+         }
+       }
+       continue;
+     }
+     nmea_parse_one_line(ibuf);
+     if (lt != last_read_time && curr_waypt && trk_head) {
+       if (curr_waypt != last_waypt) {
+         nmea_add_wpt(curr_waypt, trk_head);
+         last_waypt = curr_waypt;
+       }
+       lt = last_read_time;
+     }
+   }
+   /* try to complete date-less trackpoints */
+   nmea_fix_timestamps(trk_head);
+ }
+ void
+ nmea_rd_posn_init(const char* fname)
+ {
+   if ((gbser_handle = gbser_init(fname)) != NULL) {
+     read_mode = rm_serial;
+     gbser_set_speed(gbser_handle, 4800);
+   } else {
+     fatal(MYNAME ": Could not open '%s' for position tracking.\n", fname);
+   }
+   gbser_flush(gbser_handle);
+   if (opt_baud) {
+     if (!gbser_set_speed(gbser_handle, atoi(opt_baud))) {
+       fatal(MYNAME ": Unable to set baud rate %s\n", opt_baud);
+     }
+   }
+   posn_fname = fname;
+ }
+ static void
+ safe_print(int cnt, const char* b)
+ {
+   int i;
+   for (i = 0; i < cnt; i++) {
+     char c = isprint(b[i]) ? b[i] : '.';
+     fputc(c, stderr);
+   }
+ }
+ static void reset_sirf_to_nmea(int br);
+ static
+ int hunt_sirf(void)
+ {
+   /* Try to place the common BR's first to speed searching */
+   static int br[] = {38400, 9600, 57600, 115200, 19200, 4800, -1};
+   static int* brp = &br[0];
+   char ibuf[1024];
+   for (brp = br; *brp > 0; brp++) {
+     int rv;
+     if (global_opts.debug_level > 1) {
+       fprintf(stderr, "Trying %d\n", *brp);
+     }
+     /*
+      * Cycle our port's data speed and spray the "change to NMEA
+      * mode to the device.
+      */
+     gbser_set_speed(gbser_handle, *brp);
+     reset_sirf_to_nmea(*brp);
+     rv = gbser_read_line(gbser_handle, ibuf, sizeof(ibuf),
+                          1000, 0x0a, 0x0d);
+     /*
+      * If we didn't get a read error but did get a string that
+      * started with a dollar sign, we're probably in NMEA mode
+      * now.
+      */
+     if ((rv > -1) && (strlen(ibuf) > 0) && ibuf[0] == '$') {
+       return 1;
+     }
+     /*
+      * If nothing was received, it's not a sirf part.  Fast exit.
+      */
+     if (rv < 0) {
+       return 0;
+     }
+   }
+   return 0;
+ }
+ static Waypoint*
+ nmea_rd_posn(posn_status* posn_status)
+ {
+   char ibuf[1024];
+   static double lt = -1;
+   int i;
+   int am_sirf = 0;
+   /*
+    * Read a handful of sentences, collecting the best info we
+    * can.  If the timestamp changes (indicating the sequence is
+    * about to restart and thus the one we're collecting isn't going
+    * to get any better than we now have) hand that back to the caller.
+    */
+   for (i = 0; i < 10; i++) {
+     int rv;
+     ibuf[0] = 0;
+     rv = gbser_read_line(gbser_handle, ibuf, sizeof(ibuf), 2000, 0x0a, 0x0d);
+     if (global_opts.debug_level > 1) {
+       safe_print(strlen(ibuf), ibuf);
+     }
+     if (rv < 0) {
+       if (am_sirf == 0) {
+         if (global_opts.debug_level > 1) {
+           warning(MYNAME ": Attempting sirf mode.\n");
+         }
+         /* This is tacky, we have to change speed
+          * to 9600bps to tell it to speak NMEA at
+          * 4800.
+          */
+         am_sirf = hunt_sirf();
+         if (am_sirf) {
+           i = 0;
+           continue;
+         }
+       }
+       fatal(MYNAME ": No data received on %s.\n", posn_fname);
+     }
+     nmea_parse_one_line(ibuf);
+     if (lt != last_read_time) {
+       if (last_read_time) {
+         Waypoint* w = curr_waypt;
+         lt = last_read_time;
+         curr_waypt = NULL;
+         return w;
+       }
+     }
+   }
+   return NULL;
+ }
+ static void
+ nmea_wayptpr(const Waypoint* wpt)
+ {
+   char obuf[200];
+   double lat,lon;
+   QString s;
+   int cksum;
+   lat = degrees2ddmm(wpt->latitude);
+   lon = degrees2ddmm(wpt->longitude);
+   if (global_opts.synthesize_shortnames) {
+     s = mkshort_from_wpt(mkshort_handle, wpt);
+   } else {
+     s = mkshort(mkshort_handle, wpt->shortname);
+   }
+   snprintf(obuf, sizeof(obuf),  "GPWPL,%08.3f,%c,%09.3f,%c,%s",
+            fabs(lat), lat < 0 ? 'S' : 'N',
+            fabs(lon), lon < 0 ? 'W' : 'E', CSTRc(s)
+           );
+   cksum = nmea_cksum(obuf);
+   gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+   if (sleepus >= 0) {
+     gbfflush(file_out);
+     gb_sleep(sleepus);
+   }
+ }
+ void
+ nmea_track_init(const route_head*)
+ {
+   last_time = -1;
+ }
+ void
+ nmea_trackpt_pr(const Waypoint* wpt)
+ {
+   char obuf[200];
+   char fix='0';
+   double lat,lon;
+   int cksum;
+   struct tm* tm;
+   time_t hms;
+   time_t ymd;
+   if (opt_sleep) {
+     gbfflush(file_out);
+     if (last_time > 0) {
+       if (sleepus >= 0) {
+         gb_sleep(sleepus);
+       } else {
+         long wait_time = wpt->GetCreationTime().toTime_t() - last_time;
+         if (wait_time > 0) {
+           gb_sleep(wait_time * 1000000);
+         }
+       }
+     }
+     last_time = wpt->GetCreationTime().toTime_t();
+   }
+   lat = degrees2ddmm(wpt->latitude);
+   lon = degrees2ddmm(wpt->longitude);
+   time_t ct = wpt->GetCreationTime().toTime_t();
+   tm = gmtime(&ct);
+   if (tm) {
+     hms = tm->tm_hour * 10000 + tm->tm_min * 100 + tm->tm_sec;
+     ymd = tm->tm_mday * 10000 + tm->tm_mon * 100 + tm->tm_year;
+   } else {
+     hms = 0;
+     ymd = 0;
+   }
+   switch (wpt->fix) {
+   case fix_dgps:
+     fix='2';
+     break;
+   case fix_3d:
+   case fix_2d:
+     fix='1';
+     break;
+   case fix_pps:
+     fix='3';
+     break;
+   default:
+     fix='0';
+   }
+   if (opt_gprmc) {
+     snprintf(obuf, sizeof(obuf), "GPRMC,%010.3f,%c,%08.3f,%c,%09.3f,%c,%.2f,%.2f,%06d,,",
+              (double) hms + (wpt->GetCreationTime().time().msec() / 1000.0),
+              fix=='0' ? 'V' : 'A',
+              fabs(lat), lat < 0 ? 'S' : 'N',
+              fabs(lon), lon < 0 ? 'W' : 'E',
+              WAYPT_HAS(wpt, speed) ? MPS_TO_KNOTS(wpt->speed):(0),
+              WAYPT_HAS(wpt, course) ? (wpt->course):(0),
+              (int) ymd);
+     cksum = nmea_cksum(obuf);
+     /* GISTeq doesn't care about the checksum, but wants this prefixed, so
+      * we can write it with abandon.
+        */
+     if (opt_gisteq) {
+       gbfprintf(file_out, "---,");
+     }
+     gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+   }
+   if (opt_gpgga) {
+     snprintf(obuf, sizeof(obuf), "GPGGA,%010.3f,%08.3f,%c,%09.3f,%c,%c,%02d,%.1f,%.3f,M,%.1f,M,,",
+              (double) hms + (wpt->GetCreationTime().time().msec() / 1000.0),
+              fabs(lat), lat < 0 ? 'S' : 'N',
+              fabs(lon), lon < 0 ? 'W' : 'E',
+              fix,
+              (wpt->sat>0)?(wpt->sat):(0),
+              (wpt->hdop>0)?(wpt->hdop):(0.0),
+              wpt->altitude == unknown_alt ? 0 : wpt->altitude,
+              WAYPT_HAS(wpt, geoidheight)? (wpt->geoidheight) : (0)); /* TODO: we could look up the geoidheight if needed */
+     cksum = nmea_cksum(obuf);
+     gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+   }
+   if ((opt_gpvtg) && (WAYPT_HAS(wpt, course) || WAYPT_HAS(wpt, speed))) {
+     snprintf(obuf,sizeof(obuf),"GPVTG,%.3f,T,0,M,%.3f,N,%.3f,K",
+              WAYPT_HAS(wpt, course) ? (wpt->course):(0),
+              WAYPT_HAS(wpt, speed) ? MPS_TO_KNOTS(wpt->speed):(0),
+              WAYPT_HAS(wpt, speed) ? MPS_TO_KPH(wpt->speed):(0));
+     cksum = nmea_cksum(obuf);
+     gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+   }
+   if ((opt_gpgsa) && (wpt->fix!=fix_unknown)) {
+     switch (wpt->fix) {
+     case fix_dgps:
+       /* or */
+     case fix_3d:
+       fix='3';
+       break;
+     case fix_2d:
+       fix='2';
+       break;
+     default:
+       fix=0;
+     }
+     snprintf(obuf,sizeof(obuf),"GPGSA,A,%c,,,,,,,,,,,,,%.1f,%.1f,%.1f",
+              fix,
+              (wpt->pdop>0)?(wpt->pdop):(0),
+              (wpt->hdop>0)?(wpt->hdop):(0),
+              (wpt->vdop>0)?(wpt->vdop):(0));
+     cksum = nmea_cksum(obuf);
+     gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+   }
+   gbfflush(file_out);
+ }
+ static void
+ nmea_write(void)
+ {
+   waypt_disp_all(nmea_wayptpr);
+   track_disp_all(nmea_track_init, NULL, nmea_trackpt_pr);
+ }
+ static void
+ nmea_wr_posn_init(const char* fname)
+ {
+   nmea_wr_init(fname);
+ }
+ static void
+ nmea_wr_posn(Waypoint* wpt)
+ {
+   nmea_trackpt_pr(wpt);
+ }
+ static void
+ nmea_wr_posn_deinit(void)
+ {
+ //    nmea_wr_deinit();
+ }
+ ff_vecs_t nmea_vecs = {
+   ff_type_file,
+   {
+     (ff_cap)(ff_cap_read | ff_cap_write),
+     (ff_cap)(ff_cap_read | ff_cap_write),
+     ff_cap_none
+   },
+   nmea_rd_init,
+   nmea_wr_init,
+   nmea_rd_deinit,
+   nmea_wr_deinit,
+   nmea_read,
+   nmea_write,
+   NULL,
+   nmea_args,
+   CET_CHARSET_ASCII, 0,       /* CET-REVIEW */
+   {
+     nmea_rd_posn_init, nmea_rd_posn, nmea_rd_deinit,
+     nmea_wr_posn_init, nmea_wr_posn, nmea_wr_posn_deinit
+   }
+ };
+ /*
+  * If we later decide to implement a "real" Sirf module, this code should
+  * go there.  For now, we try a kind of heavy handed thing - if we don't
+  * see NMEA-isms from the device, we'll go on the premise that it MAY be
+  * a SiRF Star device and send it the "speak NMEA, please" command.
+  */
+ static void
+ sirf_write(unsigned char* buf)
+ {
+   int i, chksum = 0;
+   int len = buf[2] << 8 | buf[3];
+   for (i = 0; i < len; i++) {
+     chksum += buf[4 + i];
+   }
+   chksum &= 0x7fff;
+   buf[len + 4] = chksum  >> 8;
+   buf[len + 5] = chksum  & 0xff;
+   gbser_write(gbser_handle, buf, len + 8);  /* 4 at front, 4 at back */
+ }
+ static
+ void reset_sirf_to_nmea(int br)
+ {
+   static unsigned char pkt[] = {0xa0, 0xa2, 0x00, 0x18,
+                                 0x81, 0x02,
+                                 0x01, 0x01, /* GGA */
+                                 0x00, 0x00, /* suppress GLL */
+                                 0x01, 0x00, /* suppress GSA */
+                                 0x05, 0x00, /* suppress GSV */
+                                 0x01, 0x01, /* use RMC for date*/
+                                 0x00, 0x00, /* suppress VTG */
+                                 0x00, 0x01, /* output rate */
+                                 0x00, 0x01, /* unused recommended values */
+                                 0x00, 0x01,
+                                 0x00, 0x01, /* ZDA */
+                                 0x12, 0xc0, /* 4800 bps */
+                                 0x00, 0x00,  /* checksum */
+                                 0xb0, 0xb3
+                                }; /* packet end */
+   /* repopulate bit rate */
+   pkt[26] = br >> 8;
+   pkt[27] = br & 0xff;
+   sirf_write(pkt);
+   gb_sleep(250 * 1000);
+   gbser_flush(gbser_handle);
+ }